Listen to this Post
How the mentioned CVE works (technical details):
vm2 uses a bridge with mutable proxies to expose real host-realm intrinsic prototypes.
The `BaseHandler.apply()` unwraps sandbox-controlled receivers and arguments via `otherFromThis()` / `otherFromThisArguments()` and then invokes the real host function using otherReflectApply().
This allows any default-exposed host function that can surface a prototype getter to become a prototype-walking primitive (see lib/bridge.js:665-676).
`BaseHandler.get()` special-cases `__proto__` and returns the host-side descriptor or proxy target prototype.
An attacker can reuse the host `__lookupGetter__(‘__proto__’)` accessor repeatedly until the walk lands on host Object.prototype, Array.prototype, or `Function.prototype` (lib/bridge.js:590-616).
Once the attacker obtains a proxy to a host intrinsic prototype, `BaseHandler.set()` executes value = otherFromThis(value); return otherReflectSet(object, key, value) === true;.
This writes attacker-controlled data directly into the shared host object instead of keeping the mutation sandbox-local.
`BaseHandler.defineProperty()` repeats the same design with `otherReflectDefineProperty()` for descriptor-based writes (lib/bridge.js:641-649, 753-774).
Existing validation fails: the constructor filter only blocks one dangerous-property access pattern.
`setPrototypeOf()` only blocks prototype replacement, not ordinary property assignment.
`containsDangerousConstructor()` only protects one later re-unwrapping path instead of the initial host-prototype write sink (lib/bridge.js:494-530, 595-610, 660-662).
Consequently, a sandboxed script can pollute `Object.prototype` on the host, leading to full sandbox escape.
The provided PoC uses `Buffer.apply` and `__lookupGetter__` to climb from a sandbox proxy to the host Object.prototype, then writes vm2EscapeMarker.
dailycve form:
Platform: vm2
Version: 3.9.14 below
Vulnerability: Prototype pollution escape
Severity: Critical
date: 2023-04-05
Prediction: 2023-04-07
What Undercode Say:
Check vulnerable vm2 version in a Node.js project
npm list vm2 | grep vm2@
Detect if prototype pollution is possible (proof-of-concept check)
node -e "const {VM}=require('vm2'); new VM().run('const p=Buffer.apply; const o=p(p(p(p(Buffer.of()))))[Symbol.toPrimitive]?1:0'); console.log('Vulnerable if no error')"
Monitor for unexpected global property additions in production
node -e "const orig = Object.prototype.polluted; setInterval(() => { if(Object.prototype.polluted !== orig) console.error('Prototype pollution detected'); }, 1000)"
how Exploit:
- Run sandboxed code that obtains host `__lookupGetter__` via
Buffer.apply. - Recursively call the getter to walk from sandbox proxy to host
Object.prototype. - Assign a property (e.g.,
vm2EscapeMarker) to the host prototype using the mutable proxy. - Observe the polluted property appears on host empty object
{}, confirming sandbox escape.
Protection from this CVE
- Upgrade vm2 to version 3.9.15 or higher (patch released April 2023).
- If upgrade impossible, replace vm2 with a maintained sandbox like `isolated-vm` or
secure-ecmascript. - Apply runtime hardening: freeze critical prototypes (
Object.freeze(Object.prototype)) in the host before creating any VM (may break legitimate code).
Impact:
Sandbox escape leading to full host prototype pollution.
Attacker can override built‑in method behavior (e.g., toString, hasOwnProperty) across the entire Node.js process.
May lead to remote code execution if polluted methods are invoked by privileged host code.
🎯Let’s Practice Exploiting & Learn Patching For Free:
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

