## https://sploitus.com/exploit?id=A4AABAAB-0725-54CC-9AC6-1BB2E872A80E
# cve-2026-85706
Unauthenticated arbitrary local file read in GitLab CE/EE. Affects 18.7β19.1.7, 19.2.0β19.2.5, 19.3.0β19.3.1. Fixed in 19.1.8 / 19.2.6 / 19.3.2 (2026-09-10). CVSS 10.0, reportedly exploited in the wild. Original report by `s3ntago` and this repo is just my writeup + PoC.
## disclaimer
This PoC is published for educational and defensive research purposes only, to help admins and researchers understand and test for the vulnerability. Run it exclusively against systems you own or have explicit written authorization to test. If your instance falls in the affected range, stop reading and patch to 19.1.8 / 19.2.6 / 19.3.2 first.
## how it works
Three repository endpoints (`POST :id/repository/commits`, `POST`/`PUT :id/repository/files/:file_path`) sit behind Workhorse's requestBodyUploader. The Rails handler reads the on-disk path straight from the raw `file.path` request field and `File.open`s it before any authentication. `require_gitlab_workhorse!` does not serve as an auth because Workhorse's signing round-tripper attaches a valid `Gitlab-Workhorse-Api-Request` JWT to every request it proxies, so anything that falls through to the generic API proxy passes that check.
The only reason this isn't instant LFI for everyone is that Workhorse is supposed to rewrite the request first. But its route regex matches against the escaped path (`EscapedPath()` plus a `path.Clean` clone that never decodes %XX), while Puma decodes %XX before Grape routing. So percent-encode any character of a static segment (`%63ommits`, `%72epository`, `%66iles`), add a trailing slash, or tack on `.json` Workhorse's regex misses, Rails still routes to the vulnerable handler. That encoding mismatch is where lies the bypass. (`//`, `/./`, `%2F`, `;` variants like this don't work cause `path.Clean` normalizes the first two and Puma refuses `%2F`.)
Then just send forged unsigned upload metadata as query params:
```
POST /api/v4/projects/1/repository/%63ommits?file=&file.path=&file.size=1&Content-Type=application/x-www-form-urlencoded
```
`file=` blank satisfies the `requires :file, WorkhorseFile` validation (blank coerces to nil). The read fires pre-auth. Getting the bytes *back* is the fun part: on the urlencoded branch the helper runs `Rack::Utils.parse_nested_query(File.read(path))` and interpolates parser errors into the 400 body. Any `%` not followed by two hex digits raises `InvalidParameterError: invalid %-encoding ()` file contents comes back inside the error message. The JSON branch (`Oj`) leaks nothing, which is why the urlencoded content type matters here.
The fix (master `0d9ce3e7`, backports `1fe30154` / `b43c8b26` / `0ff7b6b2`) adds `authenticate!` to all three endpoints plus the `/authorize` pre-step, trusts only the middleware-produced `UploadedFile` for path/size, and stops echoing parser errors. Bug was introduced in Dec 2025, which is why the affected range starts at 18.7.
## usage
```
./exploit.py --url http://localhost:8080 --file /etc/passwd
./exploit.py --url http://localhost:8080 --file /opt/gitlab/embedded/service/gitlab-rails/config/gitlab.yml
```
The script cycles through all verified bypass forms (encoded segments, trailing slash, `.json`; commits + files endpoints, POST and PUT) and classifies each response so you can tell where in the chain a probe died. Authorized targets only, obviously.
## flaws / limitations / pre-conditions
- The echo only fires if the file contains a `%` not followed by two hex chars.
- The leak regex is greedy (`(.*)` up to the last `)` in the body). Fine against the stock JSON error, will over-capture if something in front wraps the response in HTML. Proper fix is parsing the JSON message field.
- Commits API needs an anonymously readable project id (the `before` block 404s otherwise).
## references
- fix commit (master): https://gitlab.com/gitlab-org/gitlab/-/commit/0d9ce3e758a85f0690be751e213625f7902c0361
- patch release notes: https://docs.gitlab.com/releases/patches/patch-release-gitlab-19-3-2-released/
- writeup: https://securityonline.info/gitlab-vulnerabilities-cve-2026-85706-cvss-10/