#!/usr/bin/env python3 """ CVE-2026-20896 checker. Tells you if a Gitea instance you own trusts a spoofed X-WEBAUTH-USER header from an untrusted source. It sends one harmless probe to a login page and compares it to a plain request. It does not read, change, or escalate anything. Run: python3 detect.py https://gitea.example.com """ import sys import urllib.error import urllib.request class _NoRedirect(urllib.request.HTTPRedirectHandler): def redirect_request(self, *args, **kwargs): return None _opener = urllib.request.build_opener(_NoRedirect) def status(url, webauth_user=None): req = urllib.request.Request(url) if webauth_user: req.add_header("X-WEBAUTH-USER", webauth_user) try: return _opener.open(req, timeout=10).status except urllib.error.HTTPError as e: return e.code except Exception: return None def main(): if len(sys.argv) < 2: print("usage: python3 detect.py ") sys.exit(1) base = sys.argv[1].rstrip("/") page = "/user/settings" probe = "cve-2026-20896-probe" # obviously fake, easy to spot and delete baseline = status(base + page) spoofed = status(base + page, probe) print("target:", base) print(" %s no header -> HTTP %s" % (page, baseline)) print(" %s X-WEBAUTH-USER probe -> HTTP %s" % (page, spoofed)) print() if baseline is None: print("unreachable - could not get an HTTP response. check the url/port/tls.") sys.exit(1) if baseline != 200 and spoofed == 200: print("VULNERABLE") print(" the instance logged in a spoofed header from an untrusted source, so") print(" anyone on the network can impersonate a user.") print(" a probe account '%s' may now exist - an admin can delete it." % probe) print(" fix: upgrade to 1.26.3/1.26.4, or set REVERSE_PROXY_TRUSTED_PROXIES to") print(" your proxy's real ip/cidr (never *).") sys.exit(2) if baseline == spoofed: print("looks safe via this probe - the header did not change anything.") print(" heads up: if reverse-proxy auth is on but auto-registration is off, a") print(" random probe name can't log in, but an existing username still could.") print(" if you use reverse-proxy auth, check REVERSE_PROXY_TRUSTED_PROXIES yourself.") sys.exit(0) print("inconclusive (baseline=%s probe=%s) - a proxy may be adding/stripping the" % (baseline, spoofed)) print("header. check REVERSE_PROXY_TRUSTED_PROXIES in your config directly.") sys.exit(1) if __name__ == "__main__": main()