In English: https://blog.deteact.com/dql-injection

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

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

Введение

ORM — это библиотека, связывающая объекты и их атрибуты в коде с таблицами и полями в базе данных.

Абстракция ORM позволяет представлять реляционные таблицы БД в виде обычных объектов и обращаться с ними, как с объектами.

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

Для чего используют ORM?

Понятно, что отсутствие необходимости вручную писать сотни SQL-запрос упрощает процесс разработки, особенно в крупных проектах.

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

Само по себе использование ORM не является средством защиты от инъекций, но при правильном использовании библиотеки предоставляют средства для параметризованных и подготовленных запросов.

Doctrine и DQL

Существует множество ORM-библиотек для различных языков программирования и фреймворков. Остановимся подробнее на проекте Doctrine, написанном на PHP, и эксплуатации инъекций в Doctrine Query Language. Doctrine по умолчанию используется в популярном PHP-фреймворке Symfony.

Пользоваться Doctrine можно как осуществляя действия над объектами в PHP-коде (при помощи QueryBuilder), так и вручную выполняя DQL-запросы. Также возможно выполнение «сырых» запросов напрямую в SQL.

Язык DQL основан на HQL (Hibernate Query Language в Java-библиотеке Hibernate) и является подмножеством SQL, но в нём всё равно довольно много возможностей, которые могут помочь и при эксплуатации инъекций.

DQL поддерживает привычные операторы SELECT, UPDATE, DELETE, однако нет реализации операторов INSERT и UNION, выражения LIMIT (необходимо использование метода setMaxResults). Оператор UNION авторы библиотеки не стали реализовывать ввиду строгой типизации DQL (а UNION подразумевает возможность выборки данных разного типа).

Также в DQL реализована поддержка подзапросов, а также выражений JOIN, WHERE, ORDER BY, HAVING, IN и т.д.

Описание синтаксиса DQL: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/dql-doctrine-query-language.html 

Ниже приведён список встроенных функций в DQL, которые можно использовать после выражений SELECT, WHERE и HAVING. Также после выражений SELECT и GROUP BY можно использовать функции AVG, COUNT, MIN, MAX, SUM.

Как и во многих СУБД, в Doctrine можно создать собственную реализацию функции (User Defined Function) на PHP и сделать её доступной из DQL.

Инъекции в DQL

Вот как выглядит создание SQL-запроса для выборки данных в Doctrine при работе с объектами в коде:

А ниже показана разница между DQL-запросом и SQL-запросом:

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

Принципы эксплуатации DQL инъекций, конечно, не отличаются от эксплуатации SQL-инъекций но необходимо понимать, что атакующий не может полностью контролировать запрос, который будет отправлен в СУБД. Работа на самом деле идёт не с базой данных, а с моделями, поэтому, например, не получится извлекать данные из таблиц, для которых не определены модели в коде.

Посмотрим, что происходит при создании такого запроса (QueryBuilder вызван из метода класса Post):

DQL-запрос преобразуется в синтаксическое дерево, после чего генерируется уже SQL-запрос в грамматике подключённой СУБД.

Техники инъекций

В зависимости от используемой СУБД, типа запроса, контекста инъекции и настроек (наличие debug-режима), возможны различные алгоритмы эксплуатации инъекции, такие как Boolean Based и Error Based.

  • Boolean Based

Функция substring и подзапросы дают возможность перебором посимвольно извлекать значения атрибутов моделей:

Из скриншотов видно, что мы получили значение первого символа хеш-суммы пароля («$»). При этом в операторе SELECT мы использовали полное имя модели User. Нет простого способа получить перечень всех моделей.

  • Error Based (SQLite)

При использовании СУБД SQLite есть ещё одна особенность — диалект SQLite достаточно бедный, а DQL обеспечивает одинаковый интерфейс независимо от используемой СУБД. Поэтому, при отсутствии каких-то нативных функций в SQLite, приходится писать их реализацию на PHP.

Это касается функций udfSqrt, udfMod, udfLocate (соответствующие DQL-функции: SQRT, MOD, LOCATE). При передаче некорректных данных в эти функции возникает исключение на уровне PHP, а не на уровне СУБД, поэтому, при отображении ошибок, возможна утечка результата SQL-подзапроса целиком.

Ошибка:

Результат SQL-подзапроса с хешем пароля:

Понятно, что при отсутствии debug-режима приложение вряд ли отобразит эти данные, но, тем не менее, возможна эксплуатация Error Based инъекции перебором (извлекать бит информации по наличию или отсутствию внутренней ошибки).

  • Инъекции в ORDER BY

Грамматика DQL не предусматривает использование сложных выражений и подзапросов после ORDER BY и GROUP BY, так что эксплуатация инъекции в таком контексте не представляется возможной, парсер пропустит лишь литералы.

  • Инъекция в IN

В качестве аргументов выражения IN можно передать подзапрос, что даёт различные возможности для эксплуатации инъекций, например, при помощи техники Error Based:

  • Инъекция в UPDATE.

Оператор UPDATE позволяет записывать в значение атрибута модели результат выполнения подзапроса, так что можно извлечь данные целиком по стороннему каналу (записав секретные данные в таблицу с публичными данными):

Выводы

Использование ORM — не панацея от SQL-инъекций. Необходимо тщательно валидировать и санитизировать передаваемые пользователями данные, использовать подготовленные запросы.

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

Подробнее о том, какие методы являются безопасными в DQL, можно прочитать в документации: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/security.html.

Что делать?

Если вашей команде разработки не хватает помощи по организации процесса безопасности, обращайтесь к нам за услугами и продуктами в области Application Security и DevSecOps: https://appsec.deteact.com/.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *