
SonicWall Capture Labs threat research team became aware of the threat CVE-2026-47668, assessed its impact, and developed mitigation measures. The flaw, also known as the DbGate JSON Script Runner Unauthenticated Remote Code Execution, is a critical vulnerability affecting the DbGate web-based database manager (dbgate/dbgate, distributed on npm as dbgate-serve) in all versions up to and including 7.1.8. It lets an attacker run arbitrary code on the server by sending a single HTTP POST to the script runner endpoint, because an attacker-supplied field is concatenated verbatim into a JavaScript program that DbGate then executes in a forked Node.js process. Classified under CWE-94 (Code Injection), CWE-20 (Improper Input Validation), and CWE-1188 (Insecure Default Initialization), and rated CVSS 10.0 (Critical), it was reported by Ben Harvey through GitHub Security Advisory GHSA-8v3q-9vmx-36vc. Its EPSS score is 4.34% (90th percentile). The severity reaches 10.0 because authentication is effectively absent in the default deployment: the shipped anonymous provider issues a session token to any caller that asks for one. Version 7.1.9 adds identifier validation to the affected code generator, so administrators should upgrade and enable a real authentication provider.
DbGate is an open-source database manager for MySQL, PostgreSQL, SQL Server, MongoDB, SQLite, and others. It runs as a desktop application or as a self-hosted web application, and it is commonly deployed as a container alongside the databases it administers. Beyond interactive querying, it offers a script runner that performs scheduled jobs such as imports, exports, and cross-database replication. The runner accepts a declarative JSON script, compiles it into JavaScript, and executes the result.

The root cause is string concatenation in the script compiler. In packages/tools/src/ScriptWriter.ts, the assignCore method of ScriptWriterJavaScript builds a line of JavaScript source from three caller-controlled values. The props argument is passed through JSON.stringify, so it is serialized safely as data. The variableName and functionName arguments are interpolated directly into the template string with no validation, which means anything placed in them becomes program text rather than a value.

Before reaching that sink, functionName passes through compileShellApiFunctionName in packages/tools/src/packageTools.ts. That helper exists to resolve plugin-namespaced names of the form name@dbgate-plugin-xxx. When the supplied value contains no @ character, the function simply returns it prefixed with dbgateApi. It performs no check that the result is a legal JavaScript identifier, so an attacker-supplied string arrives at the code generator intact.

The reason the flaw is reachable without privilege lies in the controller. In packages/api/src/controllers/runners.js, the start method handles two kinds of script. A raw JavaScript string is gated twice, by a run-shell-script permission check and by the allowShellScripting platform flag, which is disabled in the standard npm distribution. A JSON script takes a separate branch that compiles and executes the script and then returns, so it never reaches either gate. The only check applied on that path inspects props.fileName values for disallowed directories and does not examine functionName at all. Testing against a default standalone install confirmed the consequence: a raw-string payload is rejected with "Shell scripting is not allowed", while the JSON payload executes.

Execution completes on the runner host. The generated program is launched with a wrapper template that sets require=null in an attempt to deny the script access to the module loader. That defense is bypassed by reaching the loader through the still-live module object, which is why public exploit payloads for this flaw reach child_process through process.mainModule.require.
The fix in DbGate 7.1.9 adds validation rather than changing the generator's structure. A new assertValidShellApiFunctionName helper constrains functionName to either a plain JavaScript identifier or a name@dbgate-plugin-xxx pair, and compileShellApiFunctionName calls it before use. A companion assertValidJsIdentifier helper is applied to variableName, and to the sourceVar, targetVar, and colmapVar arguments of the stream-copy generator, closing the same class of injection everywhere it appeared.

The following conditions must be met for successful exploitation of CVE-2026-47668:
Exploiting CVE-2026-47668 needs no special tooling and no credentials. The attacker first requests a session token from the login endpoint, supplying only the amoid value that selects the anonymous provider, then presents the signed bearer token that comes back on the runner request. The attacker then sends a single HTTP POST to the script runner with a JSON script whose assign command carries the payload in functionName. A semicolon closes the intended assignment, the injected statements follow, and a trailing comment marker consumes the remainder of the generated line. The server replies with an ordinary HTTP 200 containing a runid value, the same response a legitimate job produces, so a successful attack is indistinguishable from routine runner activity in the response alone.

DbGate compiles that request into uploads/<runid>.js and forks it, under the same runid the HTTP 200 returned. The generated line reads const x = await dbgateApi.x;process.mainModule.require('child_process').exec('<cmd>');//({}); The opening await dbgateApi.x does nothing and raises no error: x is not a property of the dbgateApi object, so it evaluates to undefined, and awaiting undefined is legal JavaScript. Execution continues into the injected call, and the trailing // comments out the ({}); that the generator appended. The injected command then runs in the context of the DbGate server process, which holds credentials for every database the instance manages, and which runs as root in the official container image.
Video Demonstration
| Component | Value | Purpose |
|---|---|---|
| Token Endpoint | POST /auth/login with {"amoid":"none"} | Anonymous provider signs a bearer token with no credentials |
| Target Endpoint | POST /runners/start | Compiles and executes the caller-supplied JSON script |
| Transport | plaintext HTTP on TCP 3000 | Default web listener, with no TLS unless explicitly configured |
| Script Type | "type":"json" | Selects the branch that bypasses the shell-scripting gates |
| Injection Parameter | functionName in an assign command | Interpolated verbatim into generated JavaScript source |
| Escape Sequence | x;<statements>;// | Closes the assignment, injects statements, comments out the rest |
| Execution Primitive | process.mainModule.require('child_process') | Regains the module loader that the template sets to null |
| Server Response | HTTP 200 with a runid value | A successful injection looks like a benign runner call |
To ensure SonicWall customers are prepared for any exploitation that may occur due to this vulnerability, the following signature has been released:
| Signature ID | Signature Name |
|---|---|
| IPS: 22322 | DbGate JSON Script Runner Remote Code Execution |
The risks posed by CVE-2026-47668 can be mitigated or eliminated with the following measures:
The vulnerability was reported by Ben Harvey and disclosed through the DbGate project as GitHub Security Advisory GHSA-8v3q-9vmx-36vc. The fix shipped in DbGate 7.1.9.
Third-party vulnerability database mirrors:
Share This Article

An Article By
An Article By
Security News
Security News