Веб-безопасность/Уязвимости SQLi

Материал из SecSem Wiki
Версия от 10:55, 12 октября 2020; Dzeni (обсуждение | вклад) (Новая страница: «== SQL Injection == Веб-приложения нередко используют SQL-базы данных, т. е. такие БД, взаимодейств…»)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к навигации Перейти к поиску

SQL Injection

Веб-приложения нередко используют SQL-базы данных, т. е. такие БД, взаимодействие с которыми осуществляется через запросы на языке SQL (Википедия). Поиграться с SQL можно просто в SQL Fiddle или с помощью sqlite3.

Веб-приложение формирует и отправляет базе SQL-запросы, которые могут зависеть от параметров, пришедших от клиента (взятых из HTTP-запроса). SQL-запросы текстовые и простой способ сделать запрос, зависящий от параметра - просто подставить этот параметр в текст запроса. Пример кода (на языке Python), подставляющего параметр из HTTP-запроса в SQL-запрос к базе: Sqli-code.png
Здесь "request.args.get('login')" считывает значение параметра "login" query string запроса. Вот какой вид примет запрос, если параметр login будет иметь значение "testuser".

Sql-req.png
Значение "testuser" подставилось в строковый литерал в одинарных кавычках. Говорят, что значение попало в контекст строкового литерала. После такой подстановки может оказаться, что смысл SQL-запроса изменился и данные, подставленые в запрос стали не просто значением параметра запроса (скажем, числовым или стокововым литералом), а какими-то еще конструкциями языка SQL. Вот какой запрос будет сформирован, если параметр "login" будет иметь значение ' or 1=1 --: Sqli-annot.png
В результате выражение в WHERE-части приняло вид login='' or 1=1, оно будет истинным для любой строки таблицы, в результате чего в ответ на этот запрос будут возвращены все строки таблицы. Так же можно изменить смысл запроса и передав специфическое значение параметра id для этого кода (для этого даже не придётся добавлять в значение параметра кавычку): Sql-id.png
Возможность передать такое значение параметра HTTP-запроса, которое подставится в текст SQL-запроса и при подстановке изменит смысл этого запроса называется SQL injection (OWASP). Хорошие статьи про SQL injection можно найти тут и еще тут.

Фактически, SQLi это один из представителей класса уязвимостей injection, которые получаются, если в какие-то управляющие команды подставляются параметры-данные от пользователя и эти данные могут выйти из контекста, куда подставляются, перестав быть просто данными, и поменять смысл команды.

Полезные трюки

SQL-комментарий

Супер простой трюк. В конце своей инъекции можно добавить символы однострочного SQL-комментария, тогда всё что идет дальше (как правило, остаток исходного SQL-запроса) будет считаться комментарием и проигнорируется. В разных БД символы комментария могут разными, см. документацию, чаще всего это двойной минус -- и/или решетка #, также бывает классический двойной слеш //. Фактически, этот трюк уже был использован в примере в предыдущем разделе, двойной минус в конце там заставил базу данных проигнорировать оставшуюся от исходного запроса одинарную кавычку в конце.

UNION SELECT

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

Еще при использовании UNION бывает проблема что код приложения отдаёт не все строки результата, а только часть, например только первую (так обычно бывает когда разработчик предполагал что запрос вернет только одну строку). В этом случае можно просто сделать невыполнимое условие в WHERE исходного запроса - тогда он вернёт 0 строк и в выдачу попадёт строка из присоединенного SELECT'а.

Метаданные

Как правило, при эксплуатации SQL injection мешает то что неизвестны имена таблиц, имена и типы колонок. Может помочь то что в современных базах данных как правило есть специальные таблицы с метаданными, которые содержат всю схему данных базы. У разных СУБД эти таблицы называются и устроены по-разному, про их устройство можно почитать в документации. Например, у MySQL таблицы с метаданными хранятся в базе INFORMATION_SCHEMA(документация) - информация о существующих базах данных лежит в таблице SCHEMATA, о таблицах - в TABLES, о колонках - в COLUMNS и так далее.

Fingerprinting

Для атаки бывает необходимо понимать, какая именно СУБД используется - чтобы понимать, как называются таблицы с метаданными какие функции/механизмы/особенности БД можно использовать и т. д. Если из других источников (скажем, доступных исходных кодов приложения или вывода приложением подробной информации о себе) узнать, какая СУБД, не удаётся, это можно узнать от неё самой. Во-первых, если выдаются SQL-ошибки, часто по виду ошибки, который обычно содержит числовой идентификатор, можно однозначно определить базу. Если даже ошибки не выводятся, зафингерпринтить базу можно по её реакции на конструкции SQL, которые работают только в некоторых базах или работают в разных БД по-разному. Яркими примерами является то что в MySQL можно в качестве строковой колонки указать переменную @@version и функцию version() и на её место в обоих случаях подставится версия базы, в PostgreSQL и SQLite оператор || работает как оператор конкатенации строк, при этом в PostgreSQL поддерживается база information_schema, а в SQLite - нет. Почитать про database fingerprinting можно на OWASP, здесь, и еще много где, это довольно легко гуглится.

Error-based вывод

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

Stacked queries

Иногда бывает можно в одном запросе от приложения к базе данных передать несколько SQL-запросов через ;. В этом случае SQL injection позволяет сделать уже вообще любой SQL-запрос. Доступность stacked queries зависит от БД и используемого драйвера, к примеру c базами sqlite3 и MySQL обычно нельзя, с базой PostgreSQL и стандартным питоновским драйвером psycopg2 - можно.

Изменения в БД

Часто клиентские библиотеки для баз данных (драйверы) при выполнении SQL-запроса автоматически начинают новую транзакцию (выполняя оператор BEGIN). Любые изменения, сделанные SQL-запросом в транзакции, не будут видны за пределами текущей транзакции, пока она не будет успешно завершена (оператором COMMIT). Однако, если программист делал запрос, достающий что-то из базы, то есть SELECT, ему не нужно применять никакие изменения и вполне возможно он не делал COMMIT после своего запроса - поэтому, даже если вставить какой то изменяющий базу запрос после SELECT'а, он не повлияет на ее состояние, так как не будет подтверждена транзакция. (Про то как это работает в доках psycopg2, см. про функции commit и close). Однако, эту проблему можно (по крайней мере в некоторых случаях) победить - если работают stacked queries, можно просто вставить после своего запроса оператор COMMIT, и это приведет к подтверждению транзакции (SELECT ... WHERE ... ; INSERT INTO ... ; COMMIT; --).

Ссылки