
Cadena de exploits PoC para CVE-2026-2796: escape del sandbox de WebAssembly en SpiderMonkey (confusión de tipos de firma -> R/W arbitraria -> RCE)
Cadena de exploits de prueba de concepto para CVE-2026-2796, una mala compilación JIT / confusión de tipos en la optimización de importación WebAssembly de Mozilla SpiderMonkey (componente "JavaScript: WebAssembly"). Un módulo wasm manipulado consigue lectura/escritura arbitraria de todo el proceso anfitrión y ejecución arbitraria de código nativo, escapando de la sandbox de WebAssembly.
e2acef67 ("Bug 2013165 - Fix import optimization")Cuando un módulo wasm importa una función JS, SpiderMonkey aplica una optimización en MaybeOptimizeFunctionCallBind (js/src/wasm/WasmInstance.cpp) que desenvuelve importaciones de la forma Function.prototype.call.bind(fn) y almacena directamente como invocable de la importación. No comprobaba si el valor vinculado es en sí mismo una :
fn+ if (boundThis.toObject().is<JSFunction>() &&
+ boundThis.toObject().as<JSFunction>().isWasm()) {
+ return nullptr;
+ }
Sin esa comprobación, la importación se trata como una función originalmente-wasm: se omite el wrapper JS y, con él, la comprobación de firma. Por tanto, una función wasm puede invocarse a través de un tipo de importación declarado que no coincide con su tipo real. Los valores pasan sin cambios por los registros; solo cambia su interpretación (p. ej., un i64 controlado por el atacante se usa como puntero GC (ref $t), y viceversa).
| Archivo | Etapa | Resultado |
|---|---|---|
poc-crash.js | Confusión de firma | i64.const 0xDEADBEEF desreferenciado como puntero funcref → SIGSEGV en 0xdeadbf2f |
poc-addrof.js | addrOf + fakeobj | Confusión en ambas direcciones (i64 ↔ (ref $t)) → WasmArrayObject falso (numElements_ @+16, data_ @+24, elementos en línea @+40) → lectura/escritura arbitraria en cualquier parte del proceso |
poc-recon.js | Reconocimiento de diseño | Puntero nativo de JSFunction @+0x20 → fuga de la base del binario; puntero typeDef de WasmFuncRef @+0x40 |
poc-forge.js | Flujo de control | funcref falsificado pasa la comprobación de tipo call_ref; el destino de llamada se carga desde [funcref+0x38] |
poc-rce.js | Ejecución de código | funcref falsificado → system("touch /tmp/CVE-2026-2796-PWNED") mediante system() filtrado (base del binario + entrada GOT @ base+0x11d47b0) |
La primitiva de confusión se obtiene exactamente igual que en la prueba de regresión de Mozilla (js/src/jit-test/tests/wasm/regress/bug2013165.js): importar Function.prototype.call.bind(wasmExport) en un segundo módulo cuya declaración de importación tiene una firma distinta, y luego ref.func + call_ref.
# Firefox source @ 2fbc0748c460b38fc95407a3f14c41d12fb12026 (2026-01-14,
# Firefox 148 nightly — predates the fix). Any pre-148 revision works.
cd js/src
../../configure --enable-debug --enable-optimize --without-intl-api \
--enable-project=js # objdir e.g. js/src/_obj
cd _obj && make -j8
# binary: dist/bin/js (reports "JavaScript-C149.0a1")
JS=/path/to/dist/bin/js
$JS poc/poc-crash.js # SIGSEGV at 0xdeadbf2f
$JS poc/poc-addrof.js # prints [+] arbitrary read OK / write OK
$JS poc/poc-recon.js # dumps JSFunction / WasmFuncRef memory
$JS poc/poc-forge.js # crashes with PC = planted canary
rm -f /tmp/CVE-2026-2796-PWNED
$JS poc/poc-rce.js # creates /tmp/CVE-2026-2796-PWNED via system()
En una compilación parcheada (Firefox ≥ 148), poc-crash.js en su lugar lanza TypeError: bad type — la comprobación de firma se restaura.
Los desplazamientos son para macOS arm64 (shell js, esta revisión exacta y flags de compilación): WasmArrayObject { +16 numElements, +24 data_, +40 inline data }, JSFunction native @ +0x20, WasmFuncRef { +0x40 typeDef, +0x38 call target }. Se validan empíricamente en tiempo de ejecución mediante las autopruebas de los PoCs; otras compilaciones/arquitecturas requieren re-derivación (el PoC de reconocimiento automatiza la mayor parte). Sin PAC en binarios arm64 (no arm64e); las regiones JIT no son escribibles en el momento de la llamada, por lo que la cadena secuestra un destino de llamada existente en lugar de inyectar código.
Consulta docs/full-escape.md para el análisis de la etapa 2: escapar de la sandbox del SO desde la ejecución de código del renderer mediante CVE-2026-2768 (Bug 2014101, escritura OOB de IndexedDB en el proceso padre) — el "segundo bug" de un compromiso completo de Firefox. El árbol vulnerable utilizado aquí es anterior a esa corrección también.
Esto escapa de la sandbox del motor wasm (confinamiento de memoria lineal / GC) y proporciona ejecución de código nativo en el proceso actual. En un ataque real a un navegador, esto aterriza dentro del proceso de contenido de Firefox; escapar de la sandbox del SO (el "segundo bug": confusión de IPC o exploit del kernel) es un problema aparte y no forma parte de este PoC.
javascript.options.wasm=false bloquea el vector de activación.Solo para investigación de seguridad, educación y pruebas defensivas. La vulnerabilidad está parcheada en las versiones actuales de Firefox/Thunderbird. No lo utilices contra sistemas que no poseas o para los que no tengas autorización explícita de prueba.