# CVE-2026-20896 Gitea's official Docker image (up to and including 1.26.2) ships `REVERSE_PROXY_TRUSTED_PROXIES = *` in its default config. If you turn on reverse-proxy login, that wildcard means every source IP is treated as a trusted proxy, so anyone who can reach the port can send an `X-WEBAUTH-USER` header and get logged in as whoever they want. No password, no token. I reported this to Gitea and it's fixed in 1.26.3 / 1.26.4. This repo is my own writeup, a working PoC, and a checker. It's for a patched, public bug. - CVE record: https://www.cve.org/CVERecord?id=CVE-2026-20896 (reporter: rz1027) - Gitea advisory: https://github.com/go-gitea/gitea/security/advisories/GHSA-f75j-4cw6-rmx4 - Fix / release notes: https://blog.gitea.com/release-of-1.26.3-and-1.26.4/ ## The bug Gitea supports reverse-proxy authentication: you put it behind a proxy that sets `X-WEBAUTH-USER`, and Gitea trusts that header for the username. That's fine as long as only your proxy can set it. The setting that's supposed to enforce that is `REVERSE_PROXY_TRUSTED_PROXIES`, an IP allowlist. Gitea only honors the header when the request's source IP falls inside it. The documented-safe default, the one in `app.example.ini`, is `127.0.0.0/8,::1/128`: loopback only, so out of the box just the local proxy is trusted. The official Docker image doesn't use that. Its `app.ini` template hard-codes `*` (`docker/root/etc/templates/app.ini:55`, and `docker/rootless/etc/templates/app.ini:52` for the rootless image). `*` matches every source IP, so the allowlist check does nothing. Turn on reverse-proxy login and now anyone who can reach the port can send the header, not just your proxy. With auto-registration on, the account is created on the spot. Send an admin's username and you're the admin. So the code isn't wrong, the packaged default is, and it's specific to the Docker images. A binary or self-built install that follows `app.example.ini` keeps the loopback default and isn't affected. ## Try it Needs Docker and Python 3 (standard library only, nothing to install). ```bash docker compose up -d # boots vulnerable gitea/gitea:1.26.2 # give it ~30-60s to finish first-run setup, then: python3 poc.py # random new victim, shows auto-registration python3 poc.py http://localhost:3000 admin # impersonate a chosen username docker compose down -v # clean up ``` What it looks like against the bundled image: ``` 1) /user/settings with no header -> HTTP 303 (redirect to login = not authed) 2) /user/settings with X-WEBAUTH-USER -> HTTP 200 logged in as 'pocadmin' - no password, no token, any source IP 3) /pocadmin profile page -> HTTP 200 (account created on the fly) ``` The bypass works on the web session, not the token API at `/api/v1/...`, which ignores the header. ## Check your own instance `detect.py` sends one harmless probe and compares it to a normal request. It doesn't touch anything. ```bash python3 detect.py https://gitea.example.com ``` It prints VULNERABLE, looks-safe, or inconclusive. Only run it against something you own or are allowed to test. ## Fix Upgrade to 1.26.3 / 1.26.4 or later. Reverse-proxy auth is opt-in now and the image no longer ships the wildcard. If you can't upgrade yet, set `REVERSE_PROXY_TRUSTED_PROXIES` to your proxy's actual IP or CIDR (never `*`), or turn off `ENABLE_REVERSE_PROXY_AUTHENTICATION` if you don't use it. ## Timeline - 2026-05-26: Reported privately to Gitea. - 2026-07-03: CVE-2026-20896 published, credited to rz1027. - Fixed in Gitea 1.26.3 / 1.26.4. ## Credit I found this bug and reported it to Gitea on 2026-05-26. I'm the reporter named in Gitea's advisory, GHSA-f75j-4cw6-rmx4. Some coverage credited the Exploitarium repo for this instead of me. That's wrong, and it's a separate thing from what that repo published. I've since gotten several of those blogs corrected; a few are still wrong. MIT licensed. See [LICENSE](./LICENSE).