
Exécution de code à distance dans DbGate via une injection de functionName dans le point de terminaison loadReader — CVSS 8.8
functionNameSévérité: Élevée (CVSS 8.8)
CWE: CWE-94 — Contrôle inadéquat de la génération de code (injection de code)
Affecté: dbgate-api ≤ 7.1.8 (corrigé dans 7.1.9)
Avis de sécurité: GHSA-hv83-ggc4-v385
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-48017
Crédit: Romain Deperne
Le point d'accès POST /runners/load-reader prend un paramètre functionName et l'interpole
directement dans une chaîne de modèle JavaScript qui est ensuite exécutée dans un processus fils
— sans aucune assainissement ni validation. Tout utilisateur authentifié (aucune autorisation spéciale nécessaire) peut sortir de l'appel prévu et exécuter du JavaScript arbitraire, réalisant ainsi une exécution de code à distance sur le serveur.
Le processus fils définit require = null comme bac à sable, mais cela est trivialement contourné :
reste accessible et lance un véritable processus du système d'exploitation.
process.binding("spawn_sync")J'auditais le sous-système d'exécution de DbGate pour détecter l'écart entre les chemins d'exécution protégés et non protégés. L'exécuteur start() dans runners.js:292 est correctement contrôlé — il appelle
testStandardPermission('run-shell-script') et vérifie platformInfo.allowShellScripting.
loadReader() fait quelque chose de conceptuellement similaire (elle construit et exécute un script de chargement JS) mais ne dispose d'aucune de ces vérifications. J'ai tracé le flux de données :
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}` // ← aucune assainissement
functionName est contrôlé par l'attaquant et se retrouve dans une chaîne de modèle exécutée. Le préfixe dbgateApi. est la seule chose devant lui, et il est échappable : fermer l'expression avec toString();,
injecter la charge utile, et commenter le (${props}) restant avec // donne un JS valide.
Fichier: packages/api/src/controllers/runners.js (loadReader → loaderScriptTemplate)
Fichier: packages/tools/src/packageTools.ts:33 (compileShellApiFunctionName)
// packageTools.ts:33 — functionName circule sans assainissement
return `dbgateApi.${functionName}`;
// runners.js:64 — interpolé dans le modèle de chargement exécuté
`const reader = await ${compileShellApiFunctionName(functionName)}(${JSON.stringify(props)});`
Script généré (injecté) :
const reader = await dbgateApi.toString();
process.binding("spawn_sync").spawn({ file:"/bin/sh", args:["/bin/sh","-c","id"], ... });
dbgateApi.toString//({});
compileShellApiFunctionName() a été écrit pour transformer un nom court en un chemin d'API complet
(dbgateApi.<name>), en faisant implicitement confiance à functionName pour être un identifiant. La valeur, cependant,
provient directement du corps de la requête HTTP. Parce qu'elle est concaténée dans du code source qui est ensuite
eval/exécuté dans un processus fils, cette hypothèse de confiance est une RCE. Le bac à sable require = null n'est d'aucune utilité — le process.binding("spawn_sync") interne de Node le contourne.
Correction (7.1.9): valider functionName par rapport à une liste blanche d'identifiants stricts avant de le compiler dans le script de chargement.
poc/rce_loadreader_functionname_injection.py — pilote le véritable point d'accès de bout en bout.
# avec un JWT existant
python3 poc/rce_loadreader_functionname_injection.py http://localhost:3000 <JWT> 'id > /tmp/pwned'
# ou se connecter d'abord
python3 poc/rce_loadreader_functionname_injection.py http://localhost:3000 --login admin password 'id'
Le script construit la charge utile functionName, l'envoie à POST /runners/load-reader, et l'appel
spawn_sync injecté exécute la commande dans le processus fils exécuteur.
RCE authentifié sur l'hôte API DbGate. DbGate est un outil de gestion de bases de données GUI fréquemment déployé avec un large accès réseau aux bases de données internes — l'exécution de code sur celui-ci constitue un excellent point d'appui pour pénétrer la couche de données.
Divulgué de manière responsable. PoC publié après la sortie du correctif, à destination des défenseurs et de l'ingénierie de détection.