
تحليل السبب الجذري، ومختبر Docker قابل للاستغلال، ونصوص PoC لـ CVE-2026-85706، وهو قراءة ملفات عشوائية دون مصادقة في GitLab عبر اختلاف المحلّل.
CVSS 10.0 · بدون مصادقة · مستغلة فعليًا (CISA KEV)
يسمح اختلاف في تحليل المسارات (parser differential) بين GitLab Workhorse (وكيل عكسي مكتوب بـ Go) وPuma/Grape (مكتوب بـ Ruby) لمهاجم غير مُصادَق بتجاوز عملية تسليم الرفع المُسرَّعة في Workhorse والوصول إلى ثلاث نقاط نهاية للرفع مع قيمة file.path يتحكم بها المهاجم، مما يؤدي إلى قراءة ملفات عشوائية على مضيف GitLab.
ترميز حرف واحد من files كـ %66iles يجعل مسار الرفع في Workhorse يفشل في المطابقة (فهو يطابق على المسار المُرمَّز) بينما يقوم Rails بفك ترميزه والعودة إلى المعالج الحقيقي (فهو يوجّه على المسار المفكوك الترميز). يثق المعالج بمعامل file.path الخام الذي كان من المفترض أن يستبدله Workhorse — لذا يُقرأ file.path=/etc/passwd من القرص دون أي بيانات اعتماد.
POST /api/v4/projects/1/repository/%66iles/x?file=&file.path=/etc/passwd&file.size=1
^^^^^^ Workhorse misses -> raw file.path survives to Rails
| الإصدار | |
|---|---|
| المتأثر | CE/EE 18.7 → 19.1.8، 19.2 → 19.2.6، 19.3 → 19.3.2 |
| المُصلَّح | 19.1.8 / 19.2.6 / 19.3.2 (2026-09-10) |
| المسار | المحتويات |
|---|---|
docs/ANALYSIS.md | تحليل كامل للسبب الجذري — اختلاف المحلِّل، ورمزَي JWT في Workhorse، وخلل الثقة بـ file.path الخام، وفرق التصحيح، وقناة تسريب الأخطاء المنعكسة. تم التحليل مقابل الكود المصدري الحقيقي (v19.3.1-ee مقابل v19.3.2-ee). |
lab/ | مختبر قابل للاستغلال قائم على Docker (gitlab-ce:19.3.1-ce.0) + تعليمات التشغيل. |
poc/ | detect.sh (أداة كشف وجود غير مُدمِّرة) وexploit.sh (قراءة ملفات عبر الأخطاء المنعكسة). هدف واحد، مقيّد بالتصريح. |
لست على دراية بالبنية الداخلية لـ GitLab؟ إليك ما تفعله كل طبقة — بالترتيب الذي يمر به الطلب عبرها. يوجد معجم أعمق بالمفاهيم الأساسية في
docs/ANALYSIS.md §0.
Internet
│
▼
┌────────┐ ┌───────────┐ ┌────────┐ ┌──────────────────────┐
│ NGINX │────▶│ Workhorse │────▶│ Puma │────▶│ Grape / Rails app │
│(proxy) │ │ (Go) │ │ (Ruby) │ │ (Ruby) │
└────────┘ └───────────┘ └────────┘ └──────────────────────┘
| المكوّن | ما هو |
|---|---|
| NGINX | الوكيل العكسي الخارجي. يُنهي TLS، ويقدّم الملفات الثابتة، ويُمرّر كل ما تبقّى إلى الداخل. غير مُشارك مباشرةً في هذه الثغرة. |
| Workhorse | وكيل عكسي مكتوب بـ Go خاص بـ GitLab. مهمته الأساسية تخفيف العمل الذي يكون Ruby بطيئًا فيه — خاصةً بثّ عمليات الرفع الكبيرة. بالنسبة لنقاط نهاية الرفع، يقوم Workhorse بتخزين الجسم مؤقتًا في ملف مؤقت، ويوقّع JWT، ويعيد كتابة file.path بحيث لا يرى Ruby أبدًا بايتات الرفع الخام. كما يضع JWT باسم Gitlab-Workhorse-Api-Request لكل طلب على كل طلب يُمرّره (مُثبتًا أن "هذا جاء عبر الوكيل"، وليس أن "هذا المستخدم مُصادَق"). |
| Puma | خادم تطبيق Ruby الذي يشغّل تطبيق Rails. يستقبل الطلبات من Workhorse، ويشغّل الوسائط (Rack)، ويوزّعها إلى الموجّه. |
| Rack | طبقة واجهة خادم الويب في Ruby. تتعامل وسائط Rack مع تحليل سلسلة الاستعلام، وإدارة الجلسات، والأهم هنا — التحقق من JWT الرفع الخاص بـ Workhorse وبناء كائنات UploadedFile. الدالة Rack::Utils.parse_nested_query هي التي تُسرّب رسائل خطئها محتوى الملفات في هذا الاستغلال. |
| Grape | إطار عمل REST API يستخدمه GitLab لجميع نقاط النهاية /api/v4/*. يوفّر تعريفات المسارات ومرشّحات قبلية مثل require_gitlab_workhorse! (فحص الوكيل) وauthenticate! (فحص هوية المستخدم). يعمل داخل Rails، على Puma، خلف Workhorse — لذا يرى مسار URL المفكوك الترميز. |
| Rails | إطار العمل الويب الشامل (Ruby on Rails). GitLab عبارة عن كتلة Rails أحادية — النماذج والخدمات والوسائط كلها تعمل هنا داخل Puma. |
تقع الثغرة في الفجوة بين Workhorse (الذي يطابق المسارات على المسار المُرمَّز) وGrape/Puma (الذي يوجّه على المسار المفكوك الترميز). انظر أدناه.
r.URL.EscapedPath() (المُرمَّز)؛
بينما يوجّه Puma/Grape على المسار المفكوك الترميز. %66iles ≠ التعبير النمطي files بالنسبة لـ Workhorse، لكنه
يُفكّ ترميزه إلى files بالنسبة لـ Rails.file.path إلى مسار مؤقت موقّع، ولم يضع
ترويسة Gitlab-Workhorse-Multipart-Fields — لكنه لا يزال يمرّر الطلب (مع
JWT صالح باسم Gitlab-Workhorse-Api-Request).authenticate!، وكان المعالج
يقرأ params['file.path'] مباشرةً (سلسلة المهاجم) بدلًا من params[:file] UploadedFile
المُتحقَّق منه من Workhorse والمقيّد بالمسار.Rack::Utils.parse_nested_query
("invalid %-encoding (<file bytes>)") — تعكس الاستجابة بايتات الملف
حتى أول % غير صالح.يضيف التصحيح authenticate!، وينتقل إلى UploadedFile المُتحقَّق منه، ويتوقف عن
عكس e.message. انظر docs/ANALYSIS.md §5.
# 1. Stand up the vulnerable lab (see lab/README.md for details)
cd lab && docker compose up -d # wait ~5 min for GitLab to become healthy
# 2. Non-destructive detection
../poc/detect.sh http://localhost:8929
# 3. File-read PoC against a file you're authorized to read on your own lab
../poc/exploit.sh http://localhost:8929 /var/opt/gitlab/gitlab-rails/etc/gitlab.yml
نُشر هذا لأغراض دفاعية وتعليمية: فهم ثغرة CVE مُعلنة ومُصلَّحة ومُدرجة في قائمة CISA KEV، واكتشافها، وتصحيحها. السكربتات هنا أحادية الهدف وتتطلب منك تمرير الهدف صراحةً.
localhost. لا توجّهه إلى مضيفين من أطراف ثالثة.إذا كنت تشغّل GitLab، فقم بالترقية إلى إصدار مُصلَّح — فهذا هو العلاج الحقيقي الوحيد.
gitlab-org/gitlab @ v19.3.1-ee مقابل v19.3.2-eeMIT — التحليل وكود PoC فقط. GitLab علامة تجارية لشركة GitLab Inc.؛ هذا المستودع غير تابع لشركة GitLab Inc. ولا معتمد منها.