
Proof-of-Concept, der eine Use-After-Free-Schwachstelle in Firefox' RTCEncodedFrameBase über WebRTC Encoded Transforms demonstriert, wodurch Heap-Korruption und potenzielles RCE ermöglicht werden.
PoC für eine Use-After-Free (UAF)-Schwachstelle in der Komponente RTCEncodedFrameBase von Firefox, ausnutzbar über die WebRTC Encoded Transforms API.
CVE-2025-14321: Der Destruktor von RTCEncodedFrameBase ruft DetachArrayBuffer() nicht auf, wenn der native Frame-Speicher freigegeben wird. Folglich behalten JavaScript-ArrayBuffers Zeiger auf bereits freigegebenen Speicher (dangling pointers), was beliebiges Lesen und Schreiben auf dem Heap des Firefox-Content-Prozesses ermöglicht.
Dieser PoC wurde mit Mozilla Firefox 145.0.1 unter Windows 10 getestet.
Firefox stellt codierte Video-/Audio-Frames als JavaScript-ArrayBuffers über NewArrayBufferWithUserOwnedContents() bereit. Wenn der native Frame (C++) zerstört wird, wird der Backing-Speicher des ArrayBuffers freigegeben, aber der ArrayBuffer in JavaScript wird nicht abgetrennt (detach). Dies verletzt die Ownership-Invariante und erzeugt einen dangling pointer.
Verwundbarer Code (dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp):
// ❌ Destruktor trennt den ArrayBuffer nicht ab
RTCEncodedFrameBase::~RTCEncodedFrameBase() = default;
Gefixter Code:
// ✅ Trennt den ArrayBuffer ab, bevor nativer Speicher freigegeben wird
RTCEncodedFrameBase::~RTCEncodedFrameBase() {
DetachData();
}
new Uint8Array(buf) liest Daten aus Speicher, der anderen Heap-Objekten zugewiesen wurdenew Uint8Array(buf).fill(0x41) korrumpiert benachbarte Heap-Objekte (vtables, DOM-Strukturen usw.)Diese Primitiven reichen aus, um die Schwachstelle zu demonstrieren. Mit zusätzlichem Heap-Shaping könnten sie zu Remote Code Execution (RCE) eskalieren.
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
⚠ Firefox nach Ändern der Einstellungen neu starten.
Der PoC versucht, über getUserMedia() auf die Kamera zuzugreifen. Dies ist nicht zwingend erforderlich, wenn die ICE-Einstellungen korrekt gesetzt sind. Es dient dazu, Firefox in den RFC IP handling mode 1 zu versetzen, der die Erzeugung von ICE-Host-Kandidaten in Szenarien garantiert, in denen die Voreinstellungen allein nicht ausreichen.
In der Praxis:
getUserMedia() schlägt stumm fehl (NotFoundError) ohne Prompt. ICE verbindet trotzdem dank media.peerconnection.ice.loopback = true.Web Worker funktionieren nicht über file://. Über HTTP bereitstellen:
python3 -m http.server 8080
Im verwundbaren Firefox öffnen: http://localhost:8080/poc-cve-2025-14321.html
Der PoC versucht, auf die Kamera zuzugreifen. Ist eine Kamera verfügbar und der Benutzer stimmt zu, versetzt Firefox die Sitzung in RFC IP handling mode 1. Schlägt getUserMedia() fehl (VM ohne Kamera, Berechtigung verweigert), fährt der PoC mit einem Canvas als Frame-Quelle fort – ICE verbindet trotzdem, wenn media.peerconnection.ice.loopback = true gesetzt ist.
Ein <canvas> mit 320×240 und 30fps Animation wird über captureStream(30) erzeugt. Dies liefert vorhersehbar codierte Video-Frames. Sobald ICE verbunden ist und die Kamera verwendet wurde, ersetzt der PoC den Kamera-Track über replaceTrack() durch den Canvas-Track und gibt die Kamera frei.
Es werden zwei lokale RTCPeerConnection (pc1 ↔ pc2) mit standardmäßiger SDP-Aushandlung und Trickle ICE erstellt. Die Kandidaten werden direkt zwischen den Peers ausgetauscht.
Dem Sender von pc1 wird vor der SDP-Aushandlung ein RTCRtpScriptTransform zugewiesen. Der Worker fängt jeden codierten Frame aus der Sendepipeline ab.
Für jeden der ersten 200 Frames macht der Worker Folgendes:
frame.data (ArrayBuffer, der auf nativen Speicher zeigt)leaks[])new Uint8Array(buf).fill(0x41) 4. Leitet den Frame nicht weiter → der native Wrapper wird zerstört → der Speicher wird freigegebenDurch das Nicht-Weiterleiten zerstört der GC den C++-Wrapper und gibt den nativen Speicher frei. Aber der JavaScript-ArrayBuffer behält den Zeiger – ein dangling pointer wurde erzeugt.
Ab Frame 201 liest der Worker alle 3 Frames:
0x41 ab, hat der Allokator diesen Speicher wiederverwendet → UAF bestätigt0x41 über den Buffer (Write-Primitive auf fremden Heap)Dieser Lese-/Schreibzyklus über freigegebenen Speicher verursacht fortschreitende Heap-Korrumpierung, bis Firefox den Tab zum Absturz bringt (entspricht dem SIGSEGV im Advisory).
[0.4s] ✓ Start-Track: Canvas (Fallback)
[0.4s] ✓ ICE-VERBINDUNG HERGESTELLT!
[0.5s] ▶▶▶ ERSTER CODIERTER FRAME EMPFANGEN! ◀◀◀
[0.5s] Leak #0: 1219B → 41414141414141414141414141414141
[2.7s] Leak #49: 1497B → 41414141414141414141414141414141
[9.8s] Leak #199: 1870B → 41414141414141414141414141414141
[9.9s] 200 ArrayBuffers zurückgehalten → nativer Speicher bereits freigegeben
[9.9s] → Dangling pointers aktiv, warte auf Wiederverwendung…
[ TAB-ABSTURZ — Heap-Korrumpierung ]
Hinweis: Bei vorhandener und gewährter Kamera zeigt das Log
✓ getUserMedia gewährt — ICE Modus 1 aktivund später✓ Track ersetzt: Kamera → Canvasnach der ICE-Verbindung. In VMs ohne Kamera verwendet der PoC direkt das Canvas ohne Prompt.
Der Tab-Absturz bestätigt Heap-Korrumpierung: Das Schreiben von 0x41 auf Speicher, der anderen Objekten (vtables, DOM-Knoten) zugewiesen wurde, verursacht einen ungültigen Speicherzugriff im Content-Prozess.
✓ Buffer[0] korrekt abgetrennt — Fix aktiv
ArrayBuffer detached — gefixte Version ✓
DetachData() im Destruktor macht den ArrayBuffer ungültig, bevor der native Speicher freigegeben wird, wodurch der dangling pointer eliminiert wird.
├── poc-cve-2025-14321.html # Funktionierender PoC (lesbarer Quelltext)
└── README.md # Diese Datei
Mozilla hat die Schwachstelle in Firefox 146 behoben, indem DetachData() in allen Destruktoren und Teardown-Pfaden von RTCEncodedFrameBase hinzugefügt wurde.
Commit: https://hg-edge.mozilla.org/mozilla-central/rev/1051067f6e83
⚠ NUR ZU BILDUNGS- UND FORSCHUNGSZWECKEN
Dieser PoC demonstriert die Existenz der Schwachstelle bis zur Heap-Korrumpierung (Absturz). Er implementiert kein Heap-Shaping, keinen ASLR-Bypass und keine Hijacking-Techniken, die für Codeausführung erforderlich sind. Sein Zweck ist ausschließlich die Überprüfung und das Verständnis des CVE in kontrollierten Umgebungen.