
Proof-of-concept che dimostra una vulnerabilità Use-After-Free in RTCEncodedFrameBase di Firefox tramite WebRTC Encoded Transforms, consentendo corruzione dell'heap e potenziale RCE.
PoC per una vulnerabilità Use-After-Free (UAF) nel componente RTCEncodedFrameBase di Firefox, sfruttabile tramite l’API WebRTC Encoded Transforms.
CVE-2025-14321: Il distruttore di RTCEncodedFrameBase non chiama DetachArrayBuffer() quando libera la memoria nativa del frame. Di conseguenza, gli ArrayBuffer di JavaScript mantengono puntatori a memoria già liberata (dangling pointers), consentendo lettura e scrittura arbitraria sull’heap del processo content di Firefox.
Questo PoC è stato testato su Mozilla Firefox 145.0.1 e Windows 10.
Firefox espone i frame video/audio codificati come ArrayBuffer di JavaScript tramite NewArrayBufferWithUserOwnedContents(). Quando il frame nativo (C++) viene distrutto, la memoria backing dell’ArrayBuffer viene liberata, ma l’ArrayBuffer in JavaScript non viene scollegato (detach). Ciò viola l’invarianza di ownership e genera un dangling pointer.
Codice vulnerabile (dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp):
// ❌ Il distruttore non scollega l'ArrayBuffer
RTCEncodedFrameBase::~RTCEncodedFrameBase() = default;
Codice corretto:
// ✅ Scollega l'ArrayBuffer prima di liberare la memoria nativa
RTCEncodedFrameBase::~RTCEncodedFrameBase() {
DetachData();
}
new Uint8Array(buf) legge dati da memoria riallocata ad altri oggetti dell’heapnew Uint8Array(buf).fill(0x41) corrompe oggetti adiacenti nell’heap (vtables, strutture DOM, ecc.)Queste primitive sono sufficienti per dimostrare la vulnerabilità. Con un opportuno heap shaping, potrebbero essere escalate a esecuzione remota di codice (RCE).
media.peerconnection.scripttransform.enabled = true
media.peerconnection.ice.loopback = true
media.peerconnection.ice.no_host = false
media.peerconnection.ice.relay_only = false
media.peerconnection.ice.proxy_only = false
⚠ Riavviare Firefox dopo aver modificato le preferenze.
Il PoC tenta di richiedere l’accesso alla fotocamera tramite getUserMedia(). Ciò non è obbligatorio se le preferenze ICE sono configurate correttamente. La sua funzione è promuovere Firefox alla modalità RFC IP handling mode 1, che garantisce la generazione di candidati ICE host in scenari in cui le preferenze da sole non sono sufficienti.
In pratica:
getUserMedia() fallisce silenziosamente (NotFoundError) senza mostrare prompt. ICE connette comunque grazie a media.peerconnection.ice.loopback = true.I Web Worker non funzionano da file://. Servire tramite HTTP:
python3 -m http.server 8080
Aprire in Firefox vulnerabile: http://localhost:8080/poc-cve-2025-14321.html
Il PoC tenta di richiedere l’accesso alla fotocamera. Se la fotocamera è disponibile e l’utente accetta, Firefox promuove la sessione a RFC IP handling mode 1. Se getUserMedia() fallisce (VM senza fotocamera, permesso negato), il PoC prosegue con una canvas come sorgente di frame — ICE connette comunque se media.peerconnection.ice.loopback = true è attivo.
Viene creata una <canvas> 320×240 con animazione a 30fps tramite captureStream(30). Questo genera frame video codificati in modo prevedibile. Una volta che ICE connette e se è stata usata la fotocamera, il PoC sostituisce il track della fotocamera con quello della canvas tramite replaceTrack() e rilascia la fotocamera.
Vengono create due RTCPeerConnection locali (pc1 ↔ pc2) con negoziazione SDP standard e trickle ICE. I candidati vengono scambiati direttamente tra i peer.
Viene assegnato un RTCRtpScriptTransform al sender di pc1 prima della negoziazione SDP. Il worker intercetta ogni frame codificato della pipeline di invio.
Per ciascuno dei primi 200 frame, il worker:
frame.data (ArrayBuffer che punta alla memoria nativa)leaks[])new Uint8Array(buf).fill(0x41)Non reinviando il frame, il GC distrugge il wrapper C++ e libera la memoria nativa. Ma l’ArrayBuffer di JavaScript mantiene il puntatore — dangling pointer creato.
A partire dal frame 201, ogni 3 frame il worker:
0x41, l’allocatore ha riutilizzato quella memoria → UAF confermato0x41 sul buffer (write primitive su heap altrui)Questo ciclo di lettura/scrittura su memoria liberata causa una corruzione progressiva dell’heap fino al crash della scheda di Firefox (equivalente al SIGSEGV riportato nell’advisory).
[0.4s] ✓ Track iniziale: canvas (fallback)
[0.4s] ✓ CONNESSIONE ICE STABILITA!
[0.5s] ▶▶▶ PRIMO FRAME ENCODED RICEVUTO! ◀◀◀
[0.5s] Leak #0: 1219B → 41414141414141414141414141414141
[2.7s] Leak #49: 1497B → 41414141414141414141414141414141
[9.8s] Leak #199: 1870B → 41414141414141414141414141414141
[9.9s] 200 ArrayBuffer mantenuti → memoria nativa GIÀ liberata
[9.9s] → Dangling pointer attivi, in attesa di riutilizzo…
[ CRASH DELLA SCHEDA — heap corruption]
Nota: Se la fotocamera è disponibile e il permesso è concesso, il log mostrerà
✓ getUserMedia concesso — ICE modo 1 attivoe poi✓ Track sostituito: fotocamera → canvasdopo la connessione ICE. Nelle VM senza fotocamera, il PoC usa direttamente la canvas senza prompt.
Il crash della scheda conferma la corruzione dell’heap: la scrittura di 0x41 su memoria riallocata ad altri oggetti (vtables, nodi DOM) causa un accesso a memoria non valido nel processo content.
✓ Buffer[0] scollegato correttamente — fix attivo
ArrayBuffer scollegato — versione corretta ✓
DetachData() nel distruttore invalida l’ArrayBuffer prima di liberare la memoria nativa, eliminando il dangling pointer.
├── poc-cve-2025-14321.html # PoC funzionante (codice sorgente leggibile)
└── README.md # Questo file
Mozilla ha corretto la vulnerabilità in Firefox 146 aggiungendo DetachData() in tutti i distruttori e percorsi di teardown di RTCEncodedFrameBase.
Commit: https://hg-edge.mozilla.org/mozilla-central/rev/1051067f6e83
⚠ SOLO A SCOPO EDUCATIVO E DI RICERCA
Questo PoC dimostra l’esistenza della vulnerabilità fino al punto di corruzione dell’heap (crash). Non implementa heap shaping, bypass di ASLR o tecniche di hijack necessarie per l’esecuzione di codice. Il suo scopo è esclusivamente la verifica e la comprensione del CVE in ambienti controllati.