
تجاوز XSRF في JupyterHub عبر POST لنموذج من أصل مختلف (Sec-Fetch-Mode: no-cors) — CWE-352
Sec-Fetch-Mode: no-cors)الخطورة: متوسطة
CWE: CWE-352 — تزوير الطلبات عبر المواقع (XSRF)
المتأثر: jupyterhub 4.1.0 ≤ الإصدار < 5.4.5 (تم الإصلاح في 5.4.5)
النشرة الأمنية: GHSA-m68r-v472-jgq9
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-40864
الائتمان: Romain Deperne
حماية XSRF في JupyterHub (أُعيدت هيكلتها في 4.1.0) كانت تستخدم ترويسة الطلب Sec-Fetch-Mode لتقرير
ما إذا كان الطلب من نفس الأصل. وكانت تعامل Sec-Fetch-Mode: no-cors على أنها من نفس الأصل — لكن
no-cors هي بالضبط القيمة التي يرسلها المتصفح عند إرسال نموذج "بسيط" عبر مواقع (cross-origin). ونتيجةً
لذلك، كانت POST نماذج HTML عبر المواقع إلى نقاط نهاية النماذج في Hub (/hub/spawn, /hub/accept-share)
تتجاوز فحص XSRF بالكامل.
واجهة JSON API غير متأثرة (تتطلب نوع محتوى غير بسيط، ما يفرض فحصًا مسبقًا CORS preflight). فقط نقاط نهاية نماذج HTML يمكن الوصول إليها بهذه الطريقة.
منطق XSRF يقصر المسار إلى "موثوق" لمجموعة من حالات Sec-Fetch-* تهدف إلى التقاط
التنقلات من نفس الأصل. وكانت Sec-Fetch-Mode: no-cors مُدرجة ضمن تلك المجموعة الموثوقة. لكن no-cors
هي الوضع الذي يخصصه المتصفح لنموذج <form method=POST> عادي يُرسَل إلى أصل مختلف —
وهو بالضبط ناقل CSRF الكلاسيكي الذي يُفترض أن يوقفه الرمز المميّز. إذن، أي نقطة نهاية تغيّر الحالة
تقبل جسم نموذج بسيط، وتعتمد فقط على هذا الحاجز XSRF، يمكن تزويرها عبر المواقع.
وبربط ذلك بنقاط النهاية الفعلية: /hub/spawn (تشغيل خادم الضحية) و/hub/accept-share
(جعل الضحية يقبل مشاركة من خادم المهاجم) كلاهما POST نموذج خلف هذا الحاجز.
/hub/spawn — يمكن لصفحة مهاجم تشغيل خادم المستخدم الفردي الخاص بالضحية دون موافقة
(استهلاك موارد / حالة غير متوقعة؛ لا يحصل المهاجم على وصول إلى ذلك الخادم)./hub/accept-share — عندما يكون المهاجم مستخدم JupyterHub مخوّلًا لمشاركة خادمه الخاص،
يمكنه إجبار الضحية على قبول مشاركة، ما يمنح الضحية وصولًا إلى خادم المهاجم
(خطوة تمهيدية لسيناريوهات هندسة اجتماعية / إسقاط بيانات إضافية).استخدام Sec-Fetch-Mode كمصدر حكم على الأصل غير سليم: no-cors لا تعني نفس الأصل. الإصلاح
في 5.4.5 توقف عن اعتبار no-cors على أنها من نفس الأصل. المشغّلون الذين لا يستطيعون الترقية فورًا يمكنهم
إسقاط الطلبات التي تحمل Sec-Fetch-Mode: no-cors عند الخادم العكسي (reverse proxy).
poc/csrf_spawn.html — استضفه على أي أصل للمهاجم واطلب من مستخدم JupyterHub مسجّل الدخول فتحه.
النموذج الذي يرسل نفسه تلقائيًا يصدر POST عبر المواقع إلى /hub/spawn (يرسل المتصفح
Sec-Fetch-Mode: no-cors)؛ الإصدارات الثغرة تقبله دون أي رمز _xsrf صالح وتشغّل
خادم الضحية. وجّه action إلى /hub/accept-share للنسخة الخاصة بقبول المشاركة.
1. عدّل TARGET-JUPYTERHUB في poc/csrf_spawn.html
2. قدّم الملف من أصل مهاجم (أي استضافة ثابتة)
3. افتحه في متصفح مصادَق مسبقًا على Hub الهدف
4. لاحظ تشغيل خادم الضحية دون تقديم أي رمز XSRF
تم الإفصاح بمسؤولية. نُشر إثبات المفهوم بعد صدور الإصلاح.