
Forgejo 16.0.5 - High - Remote - SSRF - OpenID discovery fetches attacker-controlled URLs
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · forgejo-openid-discovery-ssrf
Class: SSRF Reach: Remote
Forgejo 16.0.5 (8d24b7d) - Forgejo
When [openid] ENABLE_OPENID_SIGNIN is on, unauthenticated POST /user/login/openid hands the attacker URL to yohcop openid-go v1.0.1. That library's package-level helpers use http.DefaultClient. There is no hostmatcher on this path. Webhooks and migrations filter outbound HTTP. OpenID does not.
Someone who can reach the login form can drive a Yadis GET at loopback, RFC1918, CGNAT, and IMDS from the Forgejo host.
| ID | no CVE yet |
| Class | SSRF |
| Reach | Remote (unauthenticated, when OpenID sign-in is enabled) |
| CWE | CWE-918 |
| CVSS | High: 8.6 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N |
| Product | Forgejo |
| Affected | through v16.0.5 (8d24b7d) with ENABLE_OPENID_SIGNIN; leftover of Gitea CVE-2026-58314 |
| Auth | unauthenticated |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
No account. POST an OpenID URL. The Forgejo host fetches it.
| Privilege | Method / sink | Recon / integrity / availability | Lab-proven or inferred |
|---|---|---|---|
| Drive a Yadis GET at loopback | Unauth POST /user/login/openid with openid=http://127.0.0.1:18080/ | Recon (outbound HTTP from the Forgejo host) | Lab-proven. Oracle saw GET / with Accept: application/xrds+xml from 127.0.0.1 |
| Probe RFC1918, CGNAT, link-local, IMDS | Same http.DefaultClient Discover, no IP filter | Recon | Inferred from the client. Lab used loopback only |
| Repeat from a signed-in add-OpenID | POST /user/settings/security/openid also calls RedirectURL and skips allowedOpenIDURI | Recon | Inferred from the handler. Lab used the unauth login POST |
| Read response bodies in the login error | After CVE-2021-45325, errors no longer dump the fetch | Recon | Lab-proven closed. This leftover is the request, not an HTML leak |
Closed on this pin: OpenID association CSRF (openIDState session nonce), webhook/migrate hostmatcher (still used there), git followRedirects=false, migrate LookupIP CVE-2026-82556. The lab does not complete an OpenID login and does not return a shell.
The web installer checkbox defaults on (EnableOpenIDSignIn = MustBool(!InstallLock) while unlocked, then written to app.ini). After lock, an omitted key is false. The Docker app.ini template has no [openid] section, so a pre-locked image without the wizard is off unless the operator sets the key. Empty WHITELISTED_URIS allows every URI.
v16.0.5 closed the OpenID association CSRF. I sat on that surface anyway. Gitea PR 38406 / CVE-2026-58314 put a hostmatcher-filtered client into a dedicated openid-go instance. This pin still calls the package-level helpers.
Wrong turns already recorded: re-filing the association CSRF (that nonce is in tree); treating a Docker pre-locked image as the default (the wizard writes the checkbox on); expecting the old OpenID error-string leak; a reverse shell. Theatre.
python3 forgejo-openid-discovery-ssrf-Abraxas-Labs.py
or:
cd lab
./run.sh
Target only http://127.0.0.1:19330. Image codeberg.org/forgejo/forgejo:16.0.5. Oracle shares the Forgejo netns and binds 127.0.0.1:18080.
IOC post-openid status=200
IOC xrds-hits count=1
SUCCESS forgejo-openid-discovery-ssrf how=live post=/user/login/openid openid=http://127.0.0.1:18080/ path=/ accept=application/xrds+xml peer=127.0.0.1 post-status=200 FORGEJO-OPENID-DISCOVERY-WITNESS
Same as Gitea PR 38406: a hostmatcher-filtered http.Client into a dedicated openid-go instance. Keep WHITELISTED_URIS as a second gate. Block loopback, RFC1918, CGNAT, link-local, and metadata.
modules/auth/openid/openid.goGNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.