Sploitus

Exploit for CVE-2026-85706

githubexploit Β· 2026-09-13

Exploit Code

README97 lines
## https://sploitus.com/exploit?id=28D57F6F-140B-5789-A329-363AF4019105
# CVE-2026-85706

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.

## Affected

GitLab CE/EE:

- 18.7 up to 19.1.7
- 19.2.0 up to 19.2.5
- 19.3.0 up to 19.3.1

Precondition: the instance has at least one public project. Any real project
id works. No account or token needed.

## How it works

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:

1. The route only checks `require_gitlab_workhorse!` at entry. The real
   `authenticate!` sits behind `authorize_push_to_branch!`, which runs after
   the file read.
2. `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.
3. Workhorse's body-upload intercept matches `.../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.
4. With `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 ()` 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.

## Usage

```
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.

## Verified output

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
```

## Disclaimer

For authorized security testing and research only. Do not use against systems
you do not own or have explicit permission to test.