Threat intelligence, Threat Research

DbGate JSON Script Runner Unauthenticated Remote Code Execution

by Security News

DbGate JSON Script Runner
Unauthenticated RCE (CVE-2026-47668)

OVERVIEW

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.

TECHNICAL OVERVIEW

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.

figure1.png
Figure 1: DbGate /runners/start unauthenticated code injection flow to a reverse shell

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.

figure2.png
Figure 2: assignCore interpolates variableName and functionName into generated JavaScript source

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.

figure3.png
Figure 3: compileShellApiFunctionName returns dbgateApi plus the unvalidated functionName

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.

figure4.png
Figure 4: The JSON script branch returns before the shell-scripting permission gates

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.

figure5.png
Figure 5: DbGate 7.1.9 adds identifier validation to functionName and variableName

TRIGGERING THE VULNERABILITY

The following conditions must be met for successful exploitation of CVE-2026-47668:

  • Network-Reachable Web Deployment: The target must run DbGate as a web application, so that the HTTP API is reachable over the network. The default listener is TCP 3000. The desktop build is not exposed in this way.
  • Vulnerable Version: The DbGate or dbgate-serve build must be 7.1.8 or earlier. Version 7.1.9 and later reject the payload with an invalid identifier error before any code is generated.
  • Default Authentication Provider: The instance must be left on the shipped anonymous provider, selected by the amoid field on the login request, which signs a session token for any caller without checking credentials. Setting a real provider through the LOGIN and PASSWORD, AUTH_PROVIDER, OAUTH_AUTH, or AD_URL environment variables removes the free token.
  • JSON Script Type: The request must declare script.type as json. That branch of the runner controller bypasses the run-shell-script permission check and the allowShellScripting flag, both of which block the raw-string form on a standard npm deployment.
  • Module Loader Reacquisition: The payload must reach child_process through process.mainModule.require, because the generated script template sets require to null before the injected statements run.

EXPLOITATION

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.

figure6.png
Figure 6: Single unauthenticated POST carrying the functionName code injection payload

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

Key Payload Components
ComponentValuePurpose
Token EndpointPOST /auth/login with {"amoid":"none"}Anonymous provider signs a bearer token with no credentials
Target EndpointPOST /runners/startCompiles and executes the caller-supplied JSON script
Transportplaintext HTTP on TCP 3000Default web listener, with no TLS unless explicitly configured
Script Type"type":"json"Selects the branch that bypasses the shell-scripting gates
Injection ParameterfunctionName in an assign commandInterpolated verbatim into generated JavaScript source
Escape Sequencex;<statements>;//Closes the assignment, injects statements, comments out the rest
Execution Primitiveprocess.mainModule.require('child_process')Regains the module loader that the template sets to null
Server ResponseHTTP 200 with a runid valueA successful injection looks like a benign runner call

SONICWALL PROTECTIONS

To ensure SonicWall customers are prepared for any exploitation that may occur due to this vulnerability, the following signature has been released:

Signature IDSignature Name
IPS: 22322DbGate JSON Script Runner Remote Code Execution

REMEDIATION RECOMMENDATIONS

The risks posed by CVE-2026-47668 can be mitigated or eliminated with the following measures:

  • Upgrade DbGate: Move to DbGate or dbgate-serve 7.1.9 or later, where the script compiler validates identifiers before generating code. This is the direct fix and should be applied to every web deployment.
  • Enable a Real Authentication Provider: Do not leave the instance on the shipped anonymous provider. Configure LOGIN and PASSWORD, or an OAuth or Active Directory provider, so that the login endpoint stops issuing tokens to unauthenticated callers.
  • Restrict Network Exposure: Firewall TCP 3000, or the configured port, so the web interface is reachable only from trusted administrative hosts, and place it behind an authenticated reverse proxy with TLS rather than exposing it directly.
  • Monitor for Exploitation Artifacts: Inspect access logs for POST /runners/start requests whose body contains process.mainModule and child_process, and watch for unexpected script files appearing under uploads/ as <runid>.js, and alert on unexpected outbound connections from the DbGate host shortly after such a request.
  • Run DbGate with Least Privilege: Run the service as an unprivileged, dedicated account rather than root, and scope the database credentials it stores to the minimum each connection requires, so that code execution does not immediately yield full database administration.
  • Deploy IPS Signatures: Apply updated signature coverage at the perimeter and on any segment that can reach the DbGate listener on TCP 3000, and keep signature sets current.
  • Segment the Network: Isolate application servers from sensitive internal resources and implement egress filtering to detect unauthorized outbound connections.

RELEVANT LINKS

ATTRIBUTION

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

Security News

The SonicWall Capture Labs Threat Research Team gathers, analyzes and vets cross-vector threat information from the SonicWall Capture Threat network, consisting of global devices and resources, including more than 1 million security sensors in nearly 200 countries and territories. The research team identifies, analyzes, and mitigates critical vulnerabilities and malware daily through in-depth research, which drives protection for all SonicWall customers. In addition to safeguarding networks globally, the research team supports the larger threat intelligence community by releasing weekly deep technical analyses of the most critical threats to small businesses, providing critical knowledge that defenders need to protect their networks.

Related Articles

  • rclone Remote-Control API Unauthenticated Command Execution
    Read More
  • File Browser Hook Command Runner OS Command Injection
    Read More