
Базовый анализ CVE-2021-35942. SQL-инъекция в Django.
Django — это веб-фреймворк с открытым исходным кодом, написанный на Python и построенный по модели MVC (Model - View - Controller). Изначально он был создан для управления новостными веб-сайтами, принадлежащими издательской корпорации Lawrence, как программное обеспечение CMS (Content Management System).
В Django версии 3.1.x -> 3.1.13 и версии 3.2.x -> 3.2.5 существует уязвимость SQL-инъекции.
Причина этой уязвимости заключается в том, что функция фильтрации входных данных, контролируемых пользователем, в QuerySet.order_by() недостаточна для защиты от атак SQL-инъекций. Эта уязвимость может быть использована злоумышленниками для выполнения несанкционированных действий, приводящих к утечке конфиденциальных данных.
| CVE - ID | CVE-2021-35042 |
|---|---|
| Severity | 9.8 - CRITICAL |
| CWE - ID | CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') |
| Vulnerability Publication Date | 1/7/2021 |
| Affected Software | 3.1.x < 3.1.13, 3.2.x < 3.2.5 |
| Require Authentication | No required |
В Django создание таблиц и определение полей в базе данных выполняется путем объявления класса модели в файле models.py. В этом примере мы объявляем таблицу с именем Wolf и поле с именем name.


ORM-фреймворк, встроенный в Django, используется для работы с базой данных, и результатом запроса является набор, который называется QuerySet.
order_by(fields)
По умолчанию order_by() возвращает QuerySet, отсортированный в порядке, указанном в опции ordering в Meta модели. Мы можем переопределить условие order_by в каждом запросе, используя метод order_by().
Пример
wolves = Wolf.objects.order_by('-name', 'id')
Результат приведенного выше запроса будет отсортирован по убыванию по полю name, затем по возрастанию по id. Знак минуса перед именем поля name указывает на сортировку результатов по убыванию.
Следующий пример сортирует возвращаемые результаты по полю, полученному от пользователя; если значение не передано, сортировка выполняется по полю
id.
Результат

В версиях 3.1 и 3.2 Django позволяет комбинировать метод запроса с именем таблицы в запросе order_by. Это также является основной причиной данной уязвимости.
Передача имени таблицы дает нам тот же результат, что и передача обычного имени поля
cve202135042_wolf — это имя таблицы
Сначала приложение напрямую вызывает функцию order_by(); код, обрабатывающий функцию order_by(), определен в:
django/db/models/query.py

Функция order_by() выполняет две задачи
- Удаляет все текущие методы, вызываемые order_by(), и удаляет параметр по умолчанию, переданный, когда order_by получает другое значение.
- Передает параметр в order_by. Функция
add_ordering()выполняет это
def add_ordering(self, *ordering):
"""
Add items from the 'ordering' sequence to the query's "order by"
clause. These items are either field names (not column names) --
possibly with a direction prefix ('-' or '?') -- or OrderBy
expressions.
If 'ordering' is empty, clear all ordering from the query.
"""
errors = []
for item in ordering:
if isinstance(item, str):
if '.' in item:
warnings.warn(
'Passing column raw column aliases to order_by() is '
'deprecated. Wrap %r in a RawSQL expression before '
'passing it to order_by().' % item,
category=RemovedInDjango40Warning,
stacklevel=3,
)
continue
if item == '?':
continue
if item.startswith('-'):
item = item[1:]
if item in self.annotations:
continue
if self.extra and item in self.extra:
continue
# names_to_path() validates the lookup. A descriptive
# FieldError will be raise if it's not.
self.names_to_path(item.split(LOOKUP_SEP), self.model._meta)
elif not hasattr(item, 'resolve_expression'):
errors.append(item)
if getattr(item, 'contains_aggregate', False):
raise FieldError(
'Using an aggregate in order_by() without also including '
'it in annotate() is not allowed: %s' % item
)
if errors:
raise FieldError('Invalid order_by arguments: %s' % errors)
if ordering:
self.order_by += ordering
else:
self.default_ordering = False
Параметр, передаваемый в add_ordering(), представляет собой массив.
Например, когда параметр передается следующим образом:
wolves = Wolf.objects.order_by( 'name' , 'id' )В этом случае приложение преобразует его в следующий запрос к базе данных:
SELECT "cve202135042_wolf"."id", "cve202135042_wolf"."name" FROM "cve202135042_wolf" ORDER BY "cve202135042_wolf"."name" ASC, "cve202135042_wolf"."id" ASC
При передаче функция add_ordering проверяет каждый элемент массива; если это string, проверяются следующие 5 случаев:
if '.' in item:Проверяется, является ли это запросом с именем столбца, и указано ли имя таблицы для этого столбца в SQL-операторе. Если да, выдается предупреждение и выполняетсяcontinue.if item == '?':Если значение элемента — знак '?', результаты выводятся в случайном порядке, выполняетсяcontinue.if item.startswith('-'):Если элемент начинается с символа '-', результаты запроса сортируются по убыванию (DESC).if item in self.annotations:Проверяется, содержит ли он комментарий; если да, выполняетсяcontinue.if self.extra and item in self.extra:Определяется, есть ли дополнительные параметры; если есть, выполняетсяcontinue.
После 5 проверок параметр передается в функцию self.names_to_path(item.split(LOOKUP_SEP), self.model._meta) для дальнейшей проверки, является ли он допустимым именем столбца; затем, если он допустим, он добавляется в self.ordering класса Query для дальнейшей обработки.