
توثيق CVE-2025-68116
المؤلف: @x0root
الثغرة: تخزين Cross-Site Scripting (XSS) عبر التحميلات القابلة للعرض في المتصفح (SVG / HTML)
البرنامج المتأثر: FileRise (< 2.7.1)
الإصدار المصحح: 2.7.1
CVE الرسمي (تم طلبه عبر GHSA): CVE-2025-68116 (التتبع/النشرة: GHSA-35pp-ggh6-c59c)
النشرة ذات الصلة السابقة (التخفيف الأصلي الذي تم تجاوزه): GHSA-qrcv-vjvf-fr29
تقييم CVSS:
يقوم تقييم المُبلِّغ بتقييم الامتيازات المطلوبة (PR) عند نقطة الاستغلال، وليس عند نقطة زرع الثغرة.
يحدث الاستغلال عندما يصل الضحية إلى رابط مشاركة عام تم إنشاؤه، وهو ما لا يتطلب أي مصادقة أو امتيازات (PR:N).
يقوم تقييم CNA بتقييم الامتيازات المطلوبة بناءً على القدرة على تحميل ملف ضار. ومع ذلك، يعرّف CVSS v3.1 الامتيازات المطلوبة (PR) على أنها الامتيازات التي يجب أن يمتلكها المهاجم في وقت استغلال الثغرة، وليس الامتيازات المطلوبة لوضع أو تحضير الحالة الضعيفة.
وبالتالي، فإن PR:N تعكس بشكل أكثر دقة شروط الاستغلال في العالم الحقيقي، مما يؤدي إلى تصنيف شدته كحرجة (9.6).
ملاحظة: قدم النشرة GHSA-qrcv-vjvf-fr29 تخفيفًا منع عرض ملفات SVG داخل واجهة مستخدم FileRise (لوحة المعاينة). هذا التقرير يوثق تجاوزًا لهذا التخفيف—وبالتحديد نقاط نهاية التحميل/المشاركة في الخلفية—التي يتم تتبعها كـ GHSA-35pp-ggh6-c59c / CVE-2025-68116.
هذه الوثيقة هي سجل تقني كامل لـ CVE-2025-68116: تخزين XSS في FileRise استمر بعد تخفيف سابق وتم إصلاحه في النهاية في الإصدار v2.7.1. تتضمن الاكتشاف، وإثباتات المفهوم للاستغلال، والإصلاحات الفاشلة المتكررة، وتحليل دقيق للتحكم في التدفق الجذري للسبب (مع الأدلة)، والتحقق النهائي من التصحيح، وتحليل خصائص قابلية الاستغلال ذات الصلة بتقييم CVSS. يعتمد كل المحتوى أدناه على اختبارات معاد إنتاجها، وفحص المتحكم، وسلسلة النشرات العامة.
قامت نشرة سابقة، GHSA-qrcv-vjvf-fr29، بمعالجة تخزين XSS عبر تحميلات SVG عن طريق منع العرض المضمن في واجهة مستخدم FileRise. لم يعالج هذا التخفيف كيفية تقديم ملفات SVG بواسطة نقاط نهاية الخلفية مثل:
/api/file/download.php/api/file/share.phpتوثق CVE-2025-68116 (المتتبعة كـ GHSA-35pp-ggh6-c59c) تجاوزًا لتخفيف GHSA-qrcv-vjvf-fr29: يمكن للمهاجم تخزين SVG مصمم وتوصيله للضحايا عبر روابط مشاركة عامة أو سلوكيات تنزيل معينة، مما يؤدي إلى تنفيذ البرامج النصية في أصل FileRise.
للتحقق مما إذا كانت الخلفية لا تزال تعرض ملفات SVG بطريقة قابلة للعرض، قمت بتحميل SVG بسيط كإثبات مفهوم:
الوصول إلى الملف عبر:
/api/file/download.php?…/api/file/share.php?token=…أدى إلى تنفيذ alert(). تم تجاوز تخفيف GHSA-qrcv-vjvf-fr29 الأصلي (منع عرض واجهة المستخدم) عن طريق الوصول المباشر إلى نقاط النهاية هذه.
alert() هو إثبات مفهوم؛ اختبرت التأثير الهادف عن طريق جعل الحمولة تتفاعل مع واجهات برمجة التطبيقات الداخلية.
الحمولة المستخدمة في الاختبار:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">
<script type="text/javascript">
fetch('/api/upload/upload.php')
.then(response => response.text())
.then(data => alert('API Response: ' + data));
</script>
</svg>
عندما فتح مسؤول مسجل الدخول رابط مشاركة يحتوي على SVG هذا، تم تنفيذ البرنامج النصي وإجراء طلبات API موثقة. تضمنت التأثيرات الملاحظة:
{"csrf_expired":true,"csrf_token":"..."})تصنيف التأثير الموضح أثناء الاختبار:
أبلغت عن المشكلة بشكل خاص. أصدر المسؤول عدة إصلاحات تدريجية:
طوال الإصدارات v2.6.0 → v2.7.0، استمرت نقطة نهاية رابط المشاركة في تقديم SVG بطريقة سمحت بالعرض المضمن وتنفيذ البرامج النصية. يشرح تحليل السبب الجذري أدناه سبب فشل الإصلاحات السابقة في إغلاق المتجه بالكامل.
لم يكن السبب الأساسي هو رأس مفقود واحد ولكن ترتيب التحكم في التدفق والإخراج داخل shareFile() (المتحكم) الذي منع تطبيق رؤوس الأمان في العديد من مسارات التنفيذ. كان هناك فئتان من المشاكل:
exit; مبكرة متعددة أدت إلى تقصير الدالة قبل تعيين رؤوس الأمان.استخدمت فحص awk لسرد تكرارات header() و exit; داخل shareFile() حتى استدعاء readfile():
الأمر: awk '/function shareFile(/ {flag=1} /readfile(/ {flag=0} flag && /(header|exit;)/ {printf "%4d | %s\n", NR, $0}' src/controllers/FileController.php
المخرجات الملاحظة (مختصرة من تشغيلي):
1649 | header('Content-Type: application/json; charset=utf-8'); 1651 | exit; 1657 | header('Content-Type: application/json; charset=utf-8'); 1659 | exit; 1664 | header('Content-Type: application/json; charset=utf-8'); 1666 | exit; 1670 | header("Content-Type: text/html; charset=utf-8"); 1693 | exit; 1699 | header('Content-Type: application/json; charset=utf-8'); 1701 | exit; 1719 | header('Content-Type: application/json; charset=utf-8'); 1721 | exit; 1725 | header('Content-Type: application/json; charset=utf-8'); 1727 | exit;
تبدأ رؤوس الأمان (منطق التعزيز) في السطر ~1743:
1743 | header('X-Content-Type-Options: nosniff'); ... 1770 | header("Content-Disposition: attachment; ...");
نظرًا لأن الدالة تصدر رؤوسًا + exit; في وقت سابق في العديد من المسارات، لم تصل تلك الطلبات أبدًا إلى كود التعزيز الذي يحدد Content-Disposition أو nosniff أو النوع التقييدي.
في تدفق المشاركة المحمية بكلمة مرور، أصدرت الدالة HTML لمطالبة كلمة المرور مبكرًا:
if (!empty($record['password']) && empty($providedPass)) { header("Content-Type: text/html; charset=utf-8"); ... exit; }
يرسل هذا المسار Content-Type: text/html ويخرج قبل منطق تعزيز SVG، مما يتسبب في العرض المضمن في المتصفحات للمشاركات المحمية بكلمة مرور حيث لم يتم توفير كلمة مرور.
لم تقتصر الثغرة على التدفقات المحمية بكلمة مرور. طلب مشاركة غير محمية بكلمة مرور أرجع text/html أيضًا في اختباراتي:
الأمر: curl -svI "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" 2>&1 | grep -iE "content-type"
الملاحظ: < Content-Type: text/html; charset=UTF-8
هذا يؤكد أنه حتى في الحالة العامة (غير المحمية بكلمة مرور) كانت الاستجابة text/html، وعُرض SVG بشكل مضمن.
التقطت جلبًا خامًا لنقطة نهاية المشاركة تضمن تحذيرات PHP صدرت قبل تعزيز الرأس. لقطة (مختصرة):
الأمر: curl -s "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" | head -n 30
المخرجات الخام الملاحظة (مختصرة):
Deprecated: Constant FILTER_SANITIZE_STRING is deprecated in /data/data/com.termux/files/home/FileRise/src/controllers/FileController.php on line 1644
تظهر هذه التحذيرات أنه تم إنتاج مخرجات (إشعارات مهملة) قبل تعزيز الرأس، مما يجعل من المستحيل على استدعاءات header() اللاحقة أن تسري في تلك التشغيلات.
nosniff كان موجودًا، لكن لم يتم الوصول إليه في العديد من مسارات الكود بسبب الخروج المبكر والإخراج.بعد تقارير السبب الجذري، طبق المسؤول تغييرات عالجت ترتيب التحكم في التدفق والإخراج. في v2.7.1:
exit; السابقة التي تجاوزت التعزيز.التحقق النهائي (اختباري على v2.7.1):
الأمر: curl -svI "http://127.0.0.1:8080/api/file/share.php?token=fc911e48b0a30e9417a9020ef959784d" 2>&1 | grep -iE "content-type|content-disposition"
الملاحظ: < Content-Type: application/octet-stream < Content-Disposition: attachment; filename="xss-image.svg"; filename*=UTF-8''xss-image.svg
النتيجة: قام المتصفح بفرض تنزيل؛ لم يتم عرض SVG بشكل مضمن ولم يتم تنفيذ حمولة XSS. أعتبر الإصدار v2.7.1 قد حل المشكلة في بيئتي.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:L — الدرجة: 9.6 (حرج)
لقد وثقت هذا الأساس المنطقي في سلسلة النشرات وطلبت استخدام PR:N.
يعكس متجه CVSS الخاص بـ CNA نموذج تهديد مقيدًا.
يظهر الاختبار التجريبي مسار استغلال أكثر شدة وقابلية للتكرار، والذي يتماشى مع درجة أساس CVSS 3.1 أعلى بموجب قواعد التسجيل القياسية.
يُشجع المقيمون المستقلون على تقييم الشدة باستخدام شروط الاستغلال الملاحظة والموصوفة في هذه الوثيقة، مما يضمن أن الشدة العامة تعكس التأثير في العالم الحقيقي بدلاً من خط أساس ضيق النطاق.
1649 | header('Content-Type: application/json; charset=utf-8'); 1651 | exit; 1657 | header('Content-Type: application/json; charset=utf-8'); 1659 | exit; 1664 | header('Content-Type: application/json; charset=utf-8'); 1666 | exit; 1670 | header("Content-Type: text/html; charset=utf-8"); 1693 | exit; 1699 | header('Content-Type: application/json; charset=utf-8'); 1701 | exit; 1719 | header('Content-Type: application/json; charset=utf-8'); 1721 | exit; 1725 | header('Content-Type: application/json; charset=utf-8'); 1727 | exit;
تبدأ رؤوس الأمان عند ~1743: 1743 | header('X-Content-Type-Options: nosniff'); ... 1770 | header("Content-Disposition: attachment; ...");
~/FileRise $ curl -svI "http://127.0.0.1:8080/api/file/share.php?token=437d7913884ace4b94fab8ce745a686a" 2>&1 | grep -iE "content-type" < Content-Type: text/html; charset=UTF-8
Deprecated: Constant FILTER_SANITIZE_STRING is deprecated in /data/data/com.termux/files/home/FileRise/src/controllers/FileController.php on line 1644
...متبوعًا بطباعة حمولة SVG وعرضها بشكل مضمن.
~/FileRise $ curl -svI "http://127.0.0.1:8080/api/file/share.php?token=fc911e48b0a30e9417a9020ef959784d" 2>&1 | grep -iE "content-type|content-disposition" < Content-Type: application/octet-stream < Content-Disposition: attachment; filename="xss-image.svg"; filename*=UTF-8''xss-image.svg