
Ejecución remota de código en DbGate mediante inyección de functionName en el endpoint loadReader — CVSS 8.8
functionNameGravedad: Alta (CVSS 8.8)
CWE: CWE-94 — Control inadecuado de la generación de código ('Inyección de código')
Afectado: dbgate-api ≤ 7.1.8 (corregido en 7.1.9)
Aviso: GHSA-hv83-ggc4-v385
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-48017
Crédito: Romain Deperne
El endpoint POST /runners/load-reader acepta un parámetro functionName y lo interpola
directamente en una plantilla de cadena de JavaScript que luego se ejecuta en un proceso bifurcado — sin
ninguna sanitización ni validación. Cualquier usuario autenticado (sin necesidad de permisos especiales) puede salirse de
la llamada prevista y ejecutar JavaScript arbitrario, logrando ejecución remota de código en el servidor.
El runner bifurcado establece require = null como sandbox, pero process.binding("spawn_sync") sigue siendo accesible:
process.binding("spawn_sync") sigue siendo accesible y genera un proceso real del sistema operativo.
Estaba auditando el subsistema de runners de DbGate en busca de la brecha entre las rutas de ejecución de código
protegidas y no protegidas. El runner start() en runners.js:292 está debidamente restringido — llama a
testStandardPermission('run-shell-script') y comprueba platformInfo.allowShellScripting.
loadReader() hace algo conceptualmente similar (construye y ejecuta un script de carga JS) pero no tiene
ninguna de esas comprobaciones. Rastreé el flujo de datos:
runners.js:353 loadReader({ functionName, props })
runners.js:366 loaderScriptTemplate(prefix, functionName, ...)
runners.js:64 `... ${compileShellApiFunctionName(functionName)}(${JSON.stringify(props)});`
packageTools.ts:33 return `dbgateApi.${functionName}` // ← no sanitization
functionName está controlado por el atacante y termina dentro de una plantilla de cadena ejecutada. El prefijo dbgateApi.
es lo único que hay delante, y es escapable: cerrando la expresión con toString();,
inyectando el payload y comentando el (${props}) final con // se obtiene JS válido.
Archivo: packages/api/src/controllers/runners.js (loadReader → loaderScriptTemplate)
Archivo: packages/tools/src/packageTools.ts:33 (compileShellApiFunctionName)
// packageTools.ts:33 — functionName flows in unsanitized
return `dbgateApi.${functionName}`;
// runners.js:64 — interpolated into the executed loader template
`const reader = await ${compileShellApiFunctionName(functionName)}(${JSON.stringify(props)});`
Script generado (inyectado):
const reader = await dbgateApi.toString();
process.binding("spawn_sync").spawn({ file:"/bin/sh", args:["/bin/sh","-c","id"], ... });
dbgateApi.toString//({});
compileShellApiFunctionName() se escribió para convertir un nombre corto en una ruta de API totalmente cualificada
(dbgateApi.<name>), confiando implícitamente en que functionName fuera un identificador. Sin embargo, el valor
proviene directamente del cuerpo de la solicitud HTTP. Dado que se concatena en código fuente que posteriormente se
eval/bifurca, la suposición de confianza constituye un RCE. El sandbox require = null no ayuda — el
process.binding("spawn_sync") interno de Node lo elude.
Corrección (7.1.9): validar functionName contra una lista de permitidos de identificadores estrictos antes de compilarlo
en el script del cargador.
poc/rce_loadreader_functionname_injection.py — ejecuta el endpoint real de extremo a extremo.
# with an existing JWT
python3 poc/rce_loadreader_functionname_injection.py http://localhost:3000 <JWT> 'id > /tmp/pwned'
# or log in first
python3 poc/rce_loadreader_functionname_injection.py http://localhost:3000 --login admin password 'id'
El script construye el payload functionName, lo envía a POST /runners/load-reader, y la llamada spawn_sync inyectada ejecuta el comando en el proceso runner bifurcado.
RCE autenticado en el host de la API de DbGate. DbGate es una GUI de gestión de bases de datos que con frecuencia se despliega con amplio alcance de red hacia bases de datos internas — la ejecución de código en ella es un potente pivote hacia la capa de datos.
Divulgado de forma responsable. PoC publicado después de que se lanzara la corrección, para defensores e ingeniería de detección.