SQL-инъекция

Источник: «SQL injection»
В статье мы рассмотрим, что такое SQL-инъекции (SQLi), опишем несколько распространённых примеров, объясним, как находить и использовать различные виды уязвимостей SQL-инъекций, а также подведём итоги, как предотвратить SQL-инъекции.
SQL-инъекция
SQL-инъекция

Что такое SQL-инъекция (SQLi)

SQL-инъекция (SQLi) — это уязвимость в системе веб-безопасности, позволяющая злоумышленнику вмешиваться в запросы, выполняемые приложением к своей базе данных. Это может позволить злоумышленнику просматривать данные, которые он обычно не может получить. Это могут быть данные, принадлежащие другим пользователям, или любые другие данные, к которым приложение может получить доступ. Во многих случаях злоумышленник может изменять или удалять эти данные, вызывая постоянные изменения в содержимом или поведении приложения.

В некоторых ситуациях злоумышленник может усилить атаку SQL-инъекции, чтобы скомпрометировать сервер или другую внутреннюю инфраструктуру. Это также может позволить им проводить DoS атаки.

Каково влияние успешной SQL-инъекции

Успешная SQL-инъекция может привести к несанкционированному доступу к конфиденциальным данным, таким как:

За последние годы SQL-инъекции были использованы во многих громких случаях утечки данных. Они нанесли репутационный ущерб и привели к штрафам со стороны регулирующих органов. В некоторых случаях злоумышленник может получить постоянный бэкдор в системы организации, что приводит к долгосрочной угрозе, остающейся незамеченной в течение длительного времени.

Как обнаружить уязвимость SQL-инъекции

Вы можете обнаружить SQL-инъекцию вручную, используя систематический набор тестов для каждой точки входа в приложение. Для этого, как правило, необходимо отправить:

Кроме того, можно быстро и надёжно найти большинство уязвимостей SQL-инъекций с помощью Burp Scanner.

SQL-инъекции в различных частях запроса

Большинство уязвимостей SQL-инъекций возникает в выражении WHERE запроса SELECT. Большинство опытных тестировщиков знакомы с этим типом SQL-инъекций.

Однако уязвимости SQL-инъекций могут возникать в любом месте запроса и в различных типах запросов. Другими распространёнными местами возникновения SQL-инъекций являются:

Примеры SQL-инъекций

Существует множество уязвимостей, атак и техник SQL-инъекций, возникающих в различных ситуациях. К распространённым примерам SQL-инъекций относятся:

Извлечение скрытых данных

Представьте приложение интернет-магазина, отображающее товары в разных категориях. Когда пользователь нажимает на категорию Gifts, его браузер запрашивает URL-адрес:

https://insecure-website.com/products?category=Gifts

Это заставит приложение выполнить SQL-запрос, чтобы получить из базы данных подробную информацию о соответствующих продуктах:

SELECT * FROM products WHERE category = 'Gifts' AND released = 1

Этот SQL-запрос запрашивает базу данных:

Ограничение released = 1 используется для того, чтобы скрыть продукты, которые не были выпущены. Можно предположить, что для невыпущенных продуктов released = 0.

В приложении не реализована защита от SQL-инъекций. Это означает, что злоумышленник может построить, например, следующую атаку:

https://insecure-website.com/products?category=Gifts'--

В результате получится SQL-запрос:

SELECT * FROM products WHERE category = 'Gifts'--' AND released = 1

Важно отметить, что -- это индикатор комментария в SQL. Это означает, что остальная часть запроса интерпретируется как комментарий, фактически удаляя его. В данном примере это означает, что запрос больше не включает AND released = 1. В результате отображаются все продукты, включая те, которые ещё не выпущены.

Аналогичную атаку можно использовать, чтобы заставить приложение отображать все продукты в любой категории, включая категории, о которых оно не знает:

https://insecure-website.com/products?category=Gifts'+OR+1=1--

В результате получится SQL-запрос:

SELECT * FROM products WHERE category = 'Gifts' OR 1=1--' AND released = 1

Модифицированный запрос возвращает все элементы, в которых либо категория Gifts, либо 1 равно 1. Поскольку 1=1 всегда истинно, запрос возвращает все элементы.

Подмена логики приложения

Представьте приложение, позволяющее пользователям входить в систему с помощью имени пользователя и пароля. Если пользователь вводит имя пользователя wiener и пароль bluecheese, приложение проверяет учётные данные, выполняя следующий SQL-запрос:

SELECT * FROM users WHERE username = 'wiener' AND password = 'bluecheese'

Если запрос возвращает данные о пользователе, то логин будет успешным. В противном случае он будет отклонён.

В этом случае злоумышленник может войти в систему под именем любого пользователя без необходимости вводить пароль. Это можно сделать с помощью последовательности SQL-комментариев --, чтобы убрать проверку пароля из выражения WHERE запроса. Например, если ввести имя пользователя administrator'-- и пустой пароль, то получится следующий запрос:

SELECT * FROM users WHERE username = 'administrator'--' AND password = ''

Этот запрос возвращает пользователя, чьё имя пользователя равно administrator, и успешно осуществляет вход атакующего в систему под этим пользователем.

Получение данных из других таблиц базы данных

В тех случаях, когда приложение отвечает результатами SQL запроса, злоумышленник может использовать уязвимость SQL инъекции для получения данных из других таблиц в базе данных. Вы можете использовать ключевое слово UNION для выполнения дополнительного запроса SELECT и добавления его результатов к исходному запросу.

Например, если приложение выполняет следующий запрос, содержащий пользовательский ввод Gifts:

SELECT name, description FROM products WHERE category = 'Gifts'

Злоумышленник может ввести:

' UNION SELECT username, password FROM users--

Это заставит приложение вернуть все имена пользователей и пароли, а также названия и описания продуктов.

Слепые уязвимости SQL-инъекции

Многие случаи SQL-инъекций являются слепыми уязвимостями. Это означает, что приложение не возвращает результаты SQL-запроса или информацию об ошибках базы данных в ответах. Слепые уязвимости всё ещё можно использовать для получения несанкционированного доступа к данным, но соответствующие техники обычно сложнее и труднее в исполнении.

В зависимости от характера уязвимости и используемой базы данных для эксплуатации уязвимостей, связанных со слепыми SQL-инъекциями, могут использоваться следующие техники:

SQL-инъекция второго порядка

SQL-инъекция первого порядка возникает, когда приложение обрабатывает пользовательский ввод из HTTP-запроса и включает его в SQL-запрос небезопасным способом.

SQL-инъекция второго порядка возникает, когда приложение получает пользовательский ввод из HTTP-запроса и сохраняет его для дальнейшего использования. Обычно это делается путём помещения в базу данных, но на этапе хранения данных уязвимость не возникает. Позже, при обработке другого HTTP-запроса, приложение извлекает сохранённые данные и включает их в SQL-запрос небезопасным способом. По этой причине SQL-инъекции второго порядка также известны как хранимые SQL-инъекции.

SQL-инъекция второго порядка
SQL-инъекция второго порядка

SQL-инъекции второго порядка часто возникают в ситуациях, когда разработчики знают об уязвимостях SQL-инъекций, и поэтому безопасно обрабатывают первоначальное размещение введённых данных в базе данных. Когда данные впоследствии обрабатываются, они считаются безопасными, поскольку ранее были помещены в базу данных безопасным образом. В этот момент данные обрабатываются небезопасным способом, поскольку разработчик ошибочно считает их доверенными.

Исследование базы данных

Некоторые основные функции языка SQL реализованы одинаково для всех популярных платформ баз данных, поэтому многие способы обнаружения и эксплуатации уязвимостей SQL-инъекций работают одинаково на разных типах баз данных.

Однако между распространёнными базами данных также существует множество различий. Это означает, что некоторые методы обнаружения и эксплуатации SQL-инъекций работают по-разному на разных платформах. Например:

После обнаружения уязвимости SQL-инъекции часто бывает полезно получить информацию о базе данных. Эта информация может помочь в эксплуатации уязвимости.

Можно запросить сведения о версии базы данных. Разные методы работают для разных типов баз данных. Это означает, что если найден конкретный метод, который работает, можно определить тип базы данных. Например, для Oracle можно выполнить:

SELECT * FROM v$version

Также можно определить, какие таблицы базы данных существуют, и какие столбцы они содержат. Например, для большинства баз данных можно выполнить следующий запрос, содержащий список таблиц:

SELECT * FROM information_schema.tables

SQL-инъекции в различных контекстах

Атаки с использованием SQL-инъекций можно проводить с помощью любого управляемого ввода, обрабатываемого приложением как SQL-запрос. Например, некоторые веб-сайты принимают данные в формате JSON или XML и используют их для запроса к базе данных.

Различные форматы могут обеспечить различные способы обфускации атак, в противном случае блокируемых WAF и другими защитными механизмами. Слабые реализации часто ищут в запросе общие ключевые слова для SQL-инъекций, поэтому можно обойти эти фильтры, закодировав или экранировав символы в запрещённых ключевых словах. Например, следующая SQL-инъекция на основе XML использует последовательность экранирования XML для кодирования символа S в SELECT:

<stockCheck>
<productId>123</productId>
<storeId>999 &#x53;ELECT * FROM information_schema.tables</storeId>
</stockCheck>

Перед передачей в SQL-интерпретатор он будет декодирован на стороне сервера.

Как предотвратить SQL-инъекции

Большинство случаев SQL-инъекций можно предотвратить, используя параметризованные запросы вместо конкатенации строк в запросе. Параметризированные запросы также известны как prepared statements.

Следующий код уязвим для SQL-инъекций, поскольку пользовательский ввод конкатенируется непосредственно в запрос:

String query = "SELECT * FROM products WHERE category = '"+ input + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);

Этот код можно переписать таким образом, чтобы пользовательский ввод не вмешивался в структуру запроса:

PreparedStatement statement = connection.prepareStatement("SELECT * FROM products WHERE category = ?");
statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();

Параметризованные запросы можно использовать в любой ситуации, когда недоверенные данные появляются в запросе, включая выражение WHERE и значения в операторах INSERT или UPDATE. Они не могут использоваться для обработки недоверенных данных в других частях запроса, таких как имена таблиц или столбцов или выражение ORDER BY. Функциональность приложения, помещающая недоверенные данные в эти части запроса, должна использовать другой подход, например:

Чтобы параметризованный запрос был эффективен для предотвращения SQL-инъекций, строка, используемая в запросе, всегда должна быть жёстко закодированной константой. Она никогда не должна содержать никаких переменных любого происхождения. Не поддавайтесь искушению решать в каждом отдельном случае, можно ли доверять тому или иному элементу данных, и продолжать использовать конкатенацию строк в запросе для случаев, считающихся безопасными. Очень легко ошибиться в возможном происхождении данных или испортить доверенные данные изменениями в другом коде.

Дополнительные материалы

Предыдущая Статья

Null в Python: Понимание объекта Python NoneType

Следующая Статья

SQLi: Шпаргалка по SQL-инъекциям