
DbGateのloadReaderエンドポイントにおけるfunctionNameインジェクション経由のリモートコード実行 — CVSS 8.8
functionName インジェクションによるリモートコード実行重大度: 高 (CVSS 8.8)
CWE: CWE-94 — コード生成の制御不備 ('コードインジェクション')
影響を受けるバージョン: dbgate-api ≤ 7.1.8 (7.1.9 で修正)
勧告: GHSA-hv83-ggc4-v385
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-48017
発見者: Romain Deperne
POST /runners/load-reader エンドポイントは functionName パラメータを受け取り、それをサニタイズや検証なしに、フォークされたプロセスで実行される JavaScript テンプレート文字列に直接埋め込みます。認証済みユーザー(特別な権限は不要)は、意図された呼び出しから脱出して任意の JavaScript を実行し、サーバー上でリモートコード実行を達成できます。
フォークされたランナーはサンドボックスとして require = null を設定しますが、これは簡単にバイパスできます。process.binding("spawn_sync") は依然として到達可能であり、実際の OS プロセスを起動します。
DbGate のランナーサブシステムにおいて、ガードされた コード実行パスとガードされていない コード実行パスの隙間を監査していました。runners.js:292 の start() ランナーは適切にガードされており、testStandardPermission('run-shell-script') を呼び出し、platformInfo.allowShellScripting をチェックします。
loadReader() は概念的に似た処理(JS ローダースクリプトの構築と実行)を行いますが、それらのチェックは一切ありません。データフローを追跡しました:
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}` // ← サニタイズなし
functionName は攻撃者が制御可能で、実行されるテンプレート文字列内に挿入されます。前に付くのは dbgateApi. というプレフィックスのみで、これはエスケープ可能です。式を toString(); で閉じ、ペイロードを注入し、後続の (${props}) を // でコメントアウトすれば、有効な JS が生成されます。
ファイル: packages/api/src/controllers/runners.js (loadReader → loaderScriptTemplate)
ファイル: packages/tools/src/packageTools.ts:33 (compileShellApiFunctionName)
// packageTools.ts:33 — functionName がサニタイズされずに流れ込む
return `dbgateApi.${functionName}`;
// runners.js:64 — 実行されるローダーテンプレートに埋め込まれる
`const reader = await ${compileShellApiFunctionName(functionName)}(${JSON.stringify(props)});`
生成(注入)されたスクリプト:
const reader = await dbgateApi.toString();
process.binding("spawn_sync").spawn({ file:"/bin/sh", args:["/bin/sh","-c","id"], ... });
dbgateApi.toString//({});
compileShellApiFunctionName() は、短い名前を完全修飾された API パス(dbgateApi.<名前>)に変換するために書かれ、functionName が識別子であることを暗黙的に信頼していました。しかし、この値は HTTP リクエストボディから直接取得されます。その後、eval/フォークされるソースコードに連結されるため、この信頼の仮定は RCE につながります。require = null サンドボックスは効果がありません。Node の内部関数 process.binding("spawn_sync") がそれをバイパスします。
修正 (7.1.9): functionName を厳格な識別子許可リストに対して検証した上で、ローダースクリプトにコンパイルするようにしました。
poc/rce_loadreader_functionname_injection.py — 実際のエンドポイントをエンドツーエンドで駆動します。
# 既存の JWT を使用
python3 poc/rce_loadreader_functionname_injection.py http://localhost:3000 <JWT> 'id > /tmp/pwned'
# または先にログイン
python3 poc/rce_loadreader_functionname_injection.py http://localhost:3000 --login admin password 'id'
このスクリプトは functionName ペイロードを構築し、POST /runners/load-reader に送信します。注入された spawn_sync 呼び出しがフォークされたランナープロセス内でコマンドを実行します。
DbGate API ホスト上での認証済み RCE。DbGate はデータベース管理 GUI であり、内部データベースへの広範なネットワーク到達性を持って頻繁にデプロイされます。そのため、コード実行はデータ層への強力な足がかりとなります。
責任を持って開示。修正が出荷された後に PoC を公開。防御者および検知エンジニアリングのために。