
إثبات المفهوم يوضح ثغرة Use-After-Free في RTCEncodedFrameBase الخاص بـ Firefox من خلال تحويلات WebRTC المشفرة، مما يتيح إفساد الكومة وإمكانية RCE.
إثبات مفهوم (PoC) لثغرة Use-After-Free (UAF) في مكوّن RTCEncodedFrameBase في Firefox، والتي يمكن استغلالها عبر واجهة برمجة تطبيقات WebRTC Encoded Transforms.
CVE-2025-14321: لا يستدعي مُدمّر RTCEncodedFrameBase الدالة DetachArrayBuffer() عند تحرير الذاكرة الأصلية للإطار. ونتيجةً لذلك، تحتفظ مصفوفات ArrayBuffer في JavaScript بمؤشرات إلى ذاكرة تم تحريرها بالفعل (مؤشرات متدلية)، مما يسمح بالقراءة والكتابة التعسفية على كومة عملية محتوى Firefox.
تم اختبار إثبات المفهوم هذا على Mozilla Firefox 145.0.1 وWindows 10.
يعرض Firefox إطارات الفيديو/الصوت المشفّرة كمصفوفات ArrayBuffer في JavaScript عبر NewArrayBufferWithUserOwnedContents(). عندما يتم تدمير الإطار الأصلي (C++)، يتم تحرير الذاكرة الخلفية لمصفوفة ArrayBuffer، لكن مصفوفة ArrayBuffer في JavaScript لا يتم فصلها (detach). وهذا ينتهك ثباتية الملكية ويؤدي إلى إنشاء مؤشر متدلٍ.
الكود الضعيف (dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp):
// ❌ المُدمّر لا يفصل مصفوفة ArrayBuffer
RTCEncodedFrameBase::~RTCEncodedFrameBase() = default;
الكود المُصحَّح:
// ✅ فصل مصفوفة ArrayBuffer قبل تحرير الذاكرة الأصلية
RTCEncodedFrameBase::~RTCEncodedFrameBase() {
DetachData();
}
new Uint8Array(buf) يقرأ بيانات من ذاكرة تم إعادة تخصيصها لكائنات أخرى في الكومةnew Uint8Array(buf).fill(0x41) يُفسد الكائنات المجاورة في الكومة (الجداول الافتراضية، هياكل DOM، إلخ)هذه الأوليات كافية لإظهار الثغرة. مع تشكيل الكومة الإضافي، قد تتدرج لتنفيذ تعليمات برمجية عن بُعد (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
⚠ إعادة تشغيل Firefox بعد تعديل التفضيلات.
يحاول إثبات المفهوم طلب الوصول إلى الكاميرا عبر getUserMedia(). هذا ليس إلزامياً إذا تم تعيين تفضيلات ICE بشكل صحيح. وظيفته هي ترقية Firefox إلى وضع معالجة عنوان IP RFC mode 1، الذي يضمن توليد مرشحي ICE المضيف في السيناريوهات حيث لا تكفي التفضيلات وحدها.
عملياً:
getUserMedia() يفشل بصمت (NotFoundError) دون إظهار نافذة منبثقة. ICE يتصل بنفسه بفضل media.peerconnection.ice.loopback = true.لا تعمل عمال الويب (Web Workers) من file://. يجب تقديم الملف عبر HTTP:
python3 -m http.server 8080
افتح في Firefox الضعيف: http://localhost:8080/poc-cve-2025-14321.html
يحاول إثبات المفهوم طلب الوصول إلى الكاميرا. إذا كانت الكاميرا متاحة وقبل المستخدم، يُرقّي Firefox الجلسة إلى وضع معالجة عنوان IP RFC mode 1. إذا فشل getUserMedia() (جهاز افتراضي بدون كاميرا، رفض الصلاحية)، يستمر إثبات المفهوم مع عنصر canvas كمصدر للإطارات — يتصل ICE بنفسه إذا كان media.peerconnection.ice.loopback = true نشطاً.
يتم إنشاء <canvas> بحجم 320×240 مع رسوم متحركة بمعدل 30 إطاراً في الثانية عبر captureStream(30). ينتج عن ذلك إطارات فيديو مشفّرة بشكل قابل للتوقع. بمجرد اتصال ICE واستخدام الكاميرا، يستبدل إثبات المفهوم تتبع الكاميرا بتتبع canvas عبر replaceTrack() ويُحرّر الكاميرا.
يتم إنشاء اتصالين محليين RTCPeerConnection (pc1 ↔ pc2) مع تفاوض SDP قياسي وICE متدرج. يتم تبادل المرشحين مباشرة بين الأطراف.
يتم تعيين RTCRtpScriptTransform إلى المرسل (sender) في pc1 قبل تفاوض SDP. يعترض العامل كل إطار مشفّر من خط أنابيب الإرسال.
لكل من أول 200 إطار، يقوم العامل بما يلي:
frame.data (مصفوفة ArrayBuffer تشير إلى الذاكرة الأصلية)leaks[])new Uint8Array(buf).fill(0x41)بعدم إعادة الإرسال، يُدمّر جامع القمامة (GC) الغلاف C++ ويُحرّر الذاكرة الأصلية. لكن مصفوفة ArrayBuffer في JavaScript تحتفظ بالمؤشر — يتم إنشاء مؤشر متدلٍ.
بدءاً من الإطار 201، كل 3 إطارات يقوم العامل بما يلي:
0x41، فإن المُخصّص (allocator) أعاد استخدام هذه الذاكرة → تم تأكيد UAF0x41 فوق المخزن المؤقت (أولية كتابة على كومة الآخرين)تتسبب دورة القراءة/الكتابة هذه على الذاكرة المُحرَّرة في إفساد تدريجي للكومة حتى يتعطل (crash) تبويب Firefox (ما يعادل SIGSEGV المُبلَّغ عنه في الإشعار).
[0.4s] ✓ التتبع الأولي: canvas (خيار احتياطي)
[0.4s] ✓ تم تأسيس اتصال ICE!
[0.5s] ▶▶▶ تم استلام أول إطار مشفّر! ◀◀◀
[0.5s] التسرب #0: 1219B → 41414141414141414141414141414141
[2.7s] التسرب #49: 1497B → 41414141414141414141414141414141
[9.8s] التسرب #199: 1870B → 41414141414141414141414141414141
[9.9s] تم الاحتفاظ بـ 200 مصفوفة ArrayBuffer → الذاكرة الأصلية تحررت بالفعل
[9.9s] → مؤشرات متدلية نشطة، انتظار إعادة الاستخدام…
[ تعطل التبويب — إفساد الكومة ]
ملاحظة: إذا كانت الكاميرا متاحة وتم منح الإذن، سيُظهر السجل
✓ تم منح getUserMedia — وضع ICE 1 نشطثم✓ تم استبدال التتبع: كاميرا → canvasبعد اتصال ICE. في الأجهزة الافتراضية بدون كاميرا، يستخدم إثبات المفهوم canvas مباشرة دون نافذة منبثقة.
يؤكد تعطل التبويب إفساد الكومة: كتابة 0x41 على ذاكرة تم إعادة تخصيصها لكائنات أخرى (الجداول الافتراضية، عُقد DOM) تسبب وصولاً إلى ذاكرة غير صالحة في عملية المحتوى.
✓ المخزن المؤقت[0] تم فصله بشكل صحيح — الإصلاح نشط
تم فصل مصفوفة ArrayBuffer — الإصدار المُصحَّح ✓
DetachData() في المُدمّر يُبطل صلاحية مصفوفة ArrayBuffer قبل تحرير الذاكرة الأصلية، مما يزيل المؤشر المتدلٍ.
├── poc-cve-2025-14321.html # إثبات مفهوم وظيفي (كود مصدر قابل للقراءة)
└── README.md # هذا الملف
صحّحت Mozilla الثغرة في Firefox 146 بإضافة DetachData() في جميع المُدمِّرات ومسارات الإنهاء لـ RTCEncodedFrameBase.
الالتزام: https://hg-edge.mozilla.org/mozilla-central/rev/1051067f6e83
⚠ لأغراض تعليمية وبحثية فقط
يُظهر إثبات المفهوم هذا وجود الثغرة حتى نقطة إفساد الكومة (تعطل). لا ينفذ تشكيل الكومة، أو تجاوز ASLR، أو تقنيات الاختطاف اللازمة لتنفيذ التعليمات البرمجية. الغرض منه هو التحقق والفهم فقط لـ CVE في بيئات خاضعة للتحكم.