SQL-инъекция
Что такое SQL-инъекция (SQLi)
SQL-инъекция (SQLi) — это уязвимость в системе веб-безопасности, позволяющая злоумышленнику вмешиваться в запросы, выполняемые приложением к своей базе данных. Это может позволить злоумышленнику просматривать данные, которые он обычно не может получить. Это могут быть данные, принадлежащие другим пользователям, или любые другие данные, к которым приложение может получить доступ. Во многих случаях злоумышленник может изменять или удалять эти данные, вызывая постоянные изменения в содержимом или поведении приложения.
В некоторых ситуациях злоумышленник может усилить атаку SQL-инъекции, чтобы скомпрометировать сервер или другую внутреннюю инфраструктуру. Это также может позволить им проводить DoS атаки.
Каково влияние успешной SQL-инъекции
Успешная SQL-инъекция может привести к несанкционированному доступу к конфиденциальным данным, таким как:
- Пароли.
- Данные кредитных карт.
- Личная информация пользователя.
За последние годы SQL-инъекции были использованы во многих громких случаях утечки данных. Они нанесли репутационный ущерб и привели к штрафам со стороны регулирующих органов. В некоторых случаях злоумышленник может получить постоянный бэкдор в системы организации, что приводит к долгосрочной угрозе, остающейся незамеченной в течение длительного времени.
Как обнаружить уязвимость SQL-инъекции
Вы можете обнаружить SQL-инъекцию вручную, используя систематический набор тестов для каждой точки входа в приложение. Для этого, как правило, необходимо отправить:
- Символ одинарной кавычки
'
и ищите ошибки или другие аномалии. - Определённый синтаксис, специфичный для SQL, оценивающий базовое (исходное) значение точки входа и другое значение, и поищите систематические различия в ответах приложения.
- Логические условия, такие как
OR 1=1
иOR 1=2
, и поиск различий в ответах приложения. - Полезная нагрузка, предназначенная для запуска временных задержек при выполнении SQL-запроса, и поиск различий во времени, затраченном на ответ.
- Полезная нагрузка OAST, предназначенная для запуска внеполосного сетевого взаимодействия при выполнении SQL-запроса, и мониторинг всех возникающих взаимодействий.
Кроме того, можно быстро и надёжно найти большинство уязвимостей SQL-инъекций с помощью Burp Scanner.
SQL-инъекции в различных частях запроса
Большинство уязвимостей SQL-инъекций возникает в выражении WHERE
запроса SELECT
. Большинство опытных тестировщиков знакомы с этим типом SQL-инъекций.
Однако уязвимости SQL-инъекций могут возникать в любом месте запроса и в различных типах запросов. Другими распространёнными местами возникновения SQL-инъекций являются:
- В операторах
UPDATE
— в обновлённых значениях или в выраженииWHERE
. - В операторах
INSERT
— внутри вставляемых значений. - В операторах
SELECT
— в имени таблицы или столбца. - В операторах
SELECT
— в выраженииORDER BY
.
Примеры SQL-инъекций
Существует множество уязвимостей, атак и техник SQL-инъекций, возникающих в различных ситуациях. К распространённым примерам SQL-инъекций относятся:
- Извлечение скрытых данных, где можно модифицировать SQL запрос, чтобы вернуть дополнительные результаты.
- Подмена логики приложения, позволяющая изменить запрос таким образом, чтобы вмешаться в логику приложения.
- UNION-атаки, при которых можно получить данные из разных таблиц базы данных.
- Слепая SQL-инъекция, позволяющая не возвращать результаты запроса, которым вы управляете, в ответах приложения.
Извлечение скрытых данных
Представьте приложение интернет-магазина, отображающее товары в разных категориях. Когда пользователь нажимает на категорию Gifts
, его браузер запрашивает URL-адрес:
https://insecure-website.com/products?category=Gifts
Это заставит приложение выполнить SQL-запрос, чтобы получить из базы данных подробную информацию о соответствующих продуктах:
SELECT * FROM products WHERE category = 'Gifts' AND released = 1
Этот SQL-запрос запрашивает базу данных:
- всю информацию
*
- из таблицы
products
- где
category
этоGifts
- и
released
это1
Ограничение 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-инъекциями, могут использоваться следующие техники:
- Можно изменить логику запроса, чтобы вызвать заметную разницу в реакции приложения в зависимости от истинности одного условия. Это может включать внедрение нового условия в логику логических операций или условное срабатывание ошибки, например деления на ноль.
- Можно условно включить временную задержку в обработке запроса. Это позволяет сделать вывод об истинности условия на основе времени, затраченного приложением на ответ.
- Можно запустить внеполосное сетевое взаимодействие, используя технику OAST. Эта техника чрезвычайно мощная и работает в ситуациях, когда другие техники не работают. Зачастую можно напрямую перехватить данные через внеполосный канал. Например, можно поместить данные в запрос
DNS lookup
домена, контролируемого вами.
SQL-инъекция второго порядка
SQL-инъекция первого порядка возникает, когда приложение обрабатывает пользовательский ввод из HTTP-запроса и включает его в SQL-запрос небезопасным способом.
SQL-инъекция второго порядка возникает, когда приложение получает пользовательский ввод из HTTP-запроса и сохраняет его для дальнейшего использования. Обычно это делается путём помещения в базу данных, но на этапе хранения данных уязвимость не возникает. Позже, при обработке другого HTTP-запроса, приложение извлекает сохранённые данные и включает их в SQL-запрос небезопасным способом. По этой причине SQL-инъекции второго порядка также известны как хранимые SQL-инъекции.
SQL-инъекции второго порядка часто возникают в ситуациях, когда разработчики знают об уязвимостях SQL-инъекций, и поэтому безопасно обрабатывают первоначальное размещение введённых данных в базе данных. Когда данные впоследствии обрабатываются, они считаются безопасными, поскольку ранее были помещены в базу данных безопасным образом. В этот момент данные обрабатываются небезопасным способом, поскольку разработчик ошибочно считает их доверенными.
Исследование базы данных
Некоторые основные функции языка SQL реализованы одинаково для всех популярных платформ баз данных, поэтому многие способы обнаружения и эксплуатации уязвимостей SQL-инъекций работают одинаково на разных типах баз данных.
Однако между распространёнными базами данных также существует множество различий. Это означает, что некоторые методы обнаружения и эксплуатации SQL-инъекций работают по-разному на разных платформах. Например:
- Синтаксис конкатенации строк.
- Комментарии.
- Пакетные (или объединённые) запросы.
- API, специфичные для конкретной платформы.
- Сообщения об ошибках.
После обнаружения уязвимости 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 SELECT * 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-инъекций, строка, используемая в запросе, всегда должна быть жёстко закодированной константой. Она никогда не должна содержать никаких переменных любого происхождения. Не поддавайтесь искушению решать в каждом отдельном случае, можно ли доверять тому или иному элементу данных, и продолжать использовать конкатенацию строк в запросе для случаев, считающихся безопасными. Очень легко ошибиться в возможном происхождении данных или испортить доверенные данные изменениями в другом коде.