Reports
CRÍTICA[RCE]#3788482

Desbordamiento de pila en quote_name() de mariadb-dump: un servidor malicioso ejecuta código en el cliente (CVE-2025-13699)

Un servidor MySQL/MariaDB hostil puede provocar la ejecución remota de código en cualquier cliente mariadb-dump que se conecte, aprovechando que quote_name() copia sin comprobar longitud nombres de tabla devueltos por el servidor.

Resumen
Resumen en castellano de un reporte público, no una traducción literal. El código y los comandos se mantienen como en el original.

Resumen

El cliente de copias de seguridad mariadb-dump confía en que los nombres de tabla que le devuelve el servidor respetan el límite de 64 caracteres para identificadores. Ese límite lo impone el analizador del servidor, pero el protocolo de red de MySQL no lo aplica a los datos de un conjunto de resultados: un servidor malicioso puede devolver una cadena de la longitud que quiera. La función quote_name(), en client/mysqldump.cc, copia ese nombre a un búfer fijo de pila de 387 bytes duplicando cada acento grave (backtick) que encuentra, y no comprueba la longitud en ningún momento.

El resultado es un desbordamiento de pila (CWE-121) que un servidor bajo control del atacante convierte en ejecución remota de código contra el cliente. Se le asignó CVE-2025-13699 y el investigador lo evalúa con una gravedad crítica (CVSS 8.8, vector AV:N/AC:L/PR:N/UI:R).

  • Componente afectado: client/mysqldump.cc en MariaDB Server (todas las versiones hasta la rama main).
  • Versión probada: mariadb-dump 10.11.17-MariaDB (paquete Debian).
  • Fuente revisada: rama main, commit f40ea8f4.
  • Modelo de amenaza: servidor MySQL malicioso (mismo escenario que ya reconocía CVE-2025-13699).

La función vulnerable

quote_name() recorre el nombre y, por cada backtick, escribe dos bytes en la salida (lo duplica). No hay ninguna comprobación de longitud:

// client/mysqldump.cc:2227-2243
static char *quote_name(const char *name, char *buff, my_bool force)
{
  char *to= buff;
  char qtype= (opt_compatible_mode & MASK_ANSI_QUOTES) ? '\"' : '`';
  if (!force && !opt_quoted && !test_if_special_chars(name))
    return (char*) name;
  *to++= qtype;
  while (*name)
  {
    if (*name == qtype)
      *to++= qtype;       // duplica cada backtick: 1 byte de entrada -> 2 de salida
    *to++= *name++;
  }
  to[0]= qtype;
  to[1]= 0;
  return buff;
}

Los llamantes le pasan búferes de pila de NAME_LEN*2+3 = 387 bytes (con NAME_LEN = 64*3 = 192), un tamaño que da por hecho que el servidor respeta el límite de 64 caracteres. Un nombre de tabla de 400 backticks se expande a 800 caracteres; con los dos backticks envolventes son 802 bytes escritos en un búfer de 387, es decir, 415 bytes de desbordamiento que pisan el puntero de marco guardado, los registros preservados por el llamado y la dirección de retorno.

El mismo patrón en otros dos puntos

El investigador señala dos sitios más con el mismo defecto de confianza:

  • quote_for_like() (línea 2269): duplica los caracteres de barra invertida con expansión 4x (\ produce cuatro bytes), de modo que 100 barras invertidas desbordan el búfer de 410 bytes del llamante en la línea 3806.
  • strmov hacia hash_key (línea 5862): un búfer hash_key de 386 bytes almacena "base.tabla", y strmov(afterdot, table) copia el nombre de tabla devuelto por el servidor sin comprobar límites.

Pasos de reproducción

Requisitos: una máquina con mariadb-dump instalado (probado en 10.11.17) y Python 3 para el servidor falso.

1. Levantar el servidor MySQL malicioso. El script fake_mysql_server.py (adjunto al reporte) implementa un servidor mínimo del protocolo de red de MySQL que acepta cualquier credencial y, cuando mariadb-dump ejecuta SHOW FULL TABLES, devuelve un nombre de tabla formado por 400 backticks:

python3 fake_mysql_server.py
# [*] Fake MySQL server listening on port 3307
# [*] Payload: 400 backtick characters as table name
# [*] Expected overflow: 802 bytes into 387-byte buffer (415 bytes over)

2. Ejecutar mariadb-dump contra ese servidor. Es el comando de copia de seguridad habitual de un DBA; lo único distinto es que el host de destino está bajo control del atacante (situación realista ante un servidor comprometido, envenenamiento de DNS o una posición de intermediario):

mariadb-dump -h 127.0.0.1 -P 3307 -u root --password=x --skip-lock-tables testdb

3. Observar el fallo. El proceso termina con SIGSEGV (código de salida 139):

$ echo $?
139

4. Confirmar el control del puntero de instrucción. El análisis del volcado con GDB muestra que la dirección de retorno y seis registros preservados quedan sobrescritos con 0x6060606060606060 (bytes ASCII de backtick), lo que demuestra que el atacante controla adónde salta la ejecución al retornar:

$ gdb -batch -ex "bt" -core core.236
#0  0x0000557d05b3048b in ?? ()
#1  0x6060606060606060 in ?? ()   <-- dirección de retorno controlada por el atacante
#2  0x6060606060606060 in ?? ()
[... 18 marcos más de 0x6060606060606060 ...]

$ gdb -batch -ex "info registers" -core core.236
rbp  0x6060606060606060
rbx  0x6060606060606060
r12  0x6060606060606060
r13  0x6060606060606060
r14  0x6060606060606060
r15  0x6060606060606060

5. Probar la ejecución de código. El PoC adjunto exploit_rce.py compila un banco de pruebas que contiene la misma función quote_name() de producción (idéntico búfer de 387 bytes), con las mitigaciones del compilador desactivadas para aislar la primitiva de desbordamiento. Demuestra la cadena completa: el desbordamiento redirige la ejecución a un shellcode que lanza /bin/sh, confirmándose la ejecución de código con una traza de GDB (opción --evidence).

El detalle técnico que hace fiable el ataque es que el bucle while (*name) se detiene en el byte nulo, y toda dirección x86_64 válida contiene bytes nulos en su parte alta, lo que impide escribir una dirección de retorno completa de un solo golpe. El exploit resuelve esa restricción aprovechando el propio comportamiento de la función: tras el bucle, quote_name() siempre escribe dos bytes más —el backtick de cierre (0x60) y el terminador nulo (0x00)—, y el exploit los usa como parte de la dirección de destino en lugar de combatirlos.

Impacto

Un atacante que opere un servidor MySQL malicioso consigue ejecución remota de código contra cualquier cliente mariadb-dump que se conecte a él. La víctima no necesita ninguna configuración ni privilegio especial: el comando mariadb-dump por defecto contra un host controlado por el atacante dispara el desbordamiento de inmediato.

La evidencia con GDB acredita el control total del puntero de instrucción (RIP), de seis registros de propósito general (RBP, RBX, R12–R15) y de más de 200 bytes de contenido controlado en la pila —espacio de sobra para una cadena ROP—. Sobre binarios de producción con protección completa (PIE, NX, RELRO y stack canaries), llegar a la ejecución de código exige además vencer esas mitigaciones; el investigador argumenta que es factible, en particular porque la entropía de ASLR del PIE en x86_64 es de solo 12 bits (~2048 intentos de media) y cada intento es una nueva conexión al servidor malicioso.

Remediación

Añadir una comprobación de longitud al principio de quote_name(), quote_for_like() y quote_for_equal() que valide que strlen(name) <= NAME_LEN * 3 antes de procesar el nombre; si lo supera, mostrar un error y omitir la tabla. La misma comprobación debe proteger la llamada a strmov() de la línea 5862. Ejemplo para quote_name():

static char *quote_name(const char *name, char *buff, my_bool force)
{
  if (strlen(name) > NAME_LEN * 3)
  {
    fprintf(stderr, "Error: identifier name too long (%zu bytes)\n", strlen(name));
    buff[0] = '\0';
    return buff;
  }
  // ... código existente ...
}

Datos del programa

  • Programa: MariaDB
  • Investigador: byteoverride
  • Debilidad: desbordamiento de pila (CWE-121)
  • CVE: CVE-2025-13699
  • Estado: resuelto
  • Enviado: 8 de junio de 2026
  • Divulgado: 8 de septiembre de 2026