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

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

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

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

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

Какие последствия успешной атаки 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 всегда верно, запрос вернёт все элементы.

Предупреждение. Будьте осторожны при вводе условия OR 1=1 в SQL-запрос. Хотя это может быть безвредно в начальном внедряемом контексте, приложение обычно использует данные из одного запроса в нескольких разных запросах. Например, если ваше условие достигнет инструкции UPDATE или DELETE, это может привести случайной потере данных.

Подрыв логики приложения

Рассмотрим приложение позволяющее пользователям входить в систему по имени пользователя и паролю. Если пользователь отправит имя пользователя weiner и пароль 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-инъекции обычно полезно получить информацию о самой базе данных. Эта информация зачастую может проложить путь для дальнейшей эксплуатации.

Вы можете запросить сведения о версии базы данных. То, как это делается, зависит от типа базы данных, поэтому вы можете сделать вывод о типе базы данных исходя из того, какой метод работает. Например, в Oracle вы можете выполнить:

SELECT * FROM v$version

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

SELECT * FROM information_schema.tables

Уязвимости слепых SQL-инъекций

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

SQL-инъекция

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

Факторы, специфичные для базы данных

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

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

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

Большинство SQL инъекций можно предотвратить, используя параметризованные запросы (также известные как подготовленные выражения) вместо объединения строк в запросе.

Следующий код уязвим для 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-инъекций, строка, используемая в запросе всегда должна быть жёстко запрограммированной константой и никогда не должна содержать никаких переменных данных из любого источника. Не поддавайтесь искушению решать в каждом конкретном случае, является ли элемент данных доверенным, и продолжайте использовать конкатенацию строк в запросе для случаев считающихся безопасными. Слишком легко ошибиться в возможном происхождении данных или чтобы изменения в другом коде нарушили предположения о том, какие данные испорчены.

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

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

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

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

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