
Forgejo 16.0.5 – Hoch – Remote – SSRF – hostmatcher erlaubt 0.0.0.1 als öffentliche IP
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · forgejo-hostmatcher-0001-ssrf
Klasse: SSRF Reichweite: Remote
Forgejo 16.0.5 (8d24b7d) - Forgejo
Die Standardeinstellung [webhook] ALLOWED_HOST_LIST ist leer, daher baut services/webhook/deliver.go den Client mit dem eingebauten external auf. Dieses Prädikat ist Go IsGlobalUnicast() && !isPrivate(). isPrivate ist RFC1918/4193 plus der CGNAT-Ergänzung aus v16.0.2. RFC 6890 0.0.0.0/8 gehört zu keinem davon. 127.0.0.1 ist Loopback und wird verweigert. 0.0.0.1 ist erlaubt.
Ein angemeldeter Benutzer kann einen Webhook auf http://0.0.0.1 setzen und den HTTP-Body aus der Hook-Historie auslesen. 127.0.0.1 auf demselben Port wird verweigert.
| ID | noch keine CVE |
| Klasse | SSRF |
| Reichweite | Remote (authentifiziert; jeder Benutzer, der einen Repo-Webhook erstellen kann) |
| CWE | CWE-918 |
| CVSS | Hoch: 7.7 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N |
| Produkt | Forgejo |
| Betroffen | bis v16.0.5 (8d24b7d); Überbleibsel der CGNAT-Ergänzung aus v16.0.2 |
| Auth | angemeldeter Benutzer, der einen Repo-Webhook erstellen kann |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Auf einer Standardinstallation registrieren, ein Repo erstellen, einen Forgejo-Webhook auf http://0.0.0.1:<port>/... setzen, ihn testen und den Response-Body auslesen.
| Berechtigung | Methode / Sink | Recon / Integrität / Verfügbarkeit | Im Lab bewiesen oder abgeleitet |
|---|---|---|---|
Dieses-Netzwerk 0.0.0.1 an der Standard-Webhook-Allow-List vorbei wählen | POST /api/v1/repos/{owner}/{repo}/hooks dann POST .../hooks/{id}/tests mit URL http://0.0.0.1:8080/ssrf | Recon (ausgehendes HTTP vom Forgejo-Host) | Im Lab bewiesen. Historie speicherte HTTP 200 Body FORGEJO-HOSTMATCHER-0001-WITNESS |
| Die vollständige HTTP-Antwort von was auch immer geantwortet hat auslesen | GET /{owner}/{repo}/settings/hooks/{id} gibt ResponseInfo.Body aus | Recon (Full-Response-SSRF) | Im Lab bewiesen. Derselbe Witness auf der Historienseite |
Dieselbe Klasse über IPv4-gemapptes [::ffff:0.0.0.1] | Derselbe Matcher To4() dann checkIP | Recon | Abgeleitet aus hostmatcher. Lab verwendete 0.0.0.1 |
| Einen Listener erreichen, der breiter als Loopback im Forgejo-Netns gebunden hat | Linux connect() auf 0.0.0.0/8 ist dieser Host, sobald 0.0.0.1 auf lo liegt | Recon | Im Lab gegen das Sidecar-Oracle bewiesen. Keine Aussage über ein bestimmtes Cloud-IMDS |
Geschlossen beim selben Standard-Matcher: 127.0.0.1 (im Lab bewiesene Verweigerung in der Historie), 127.0.0.0/8, RFC1918, CGNAT 100.64.0.0/10, Link-Local 169.254.0.0/16 inklusive IPv4-IMDS, Unspecified 0.0.0.0. Der ausgehende Webhook erhält kein X-Forgejo-Internal-Auth / INTERNAL_TOKEN. Dies ist für sich genommen kein RCE. |
Dies ist nicht der CGNAT-Fix aus v16.0.2. Forgejo hat nie eine Gitea-artige Reserved-Net-Liste bekommen. Dieselbe Klasse wie gitea-hostmatcher-0000-ssrf.
v16.0.2 hat CGNAT in private aufgenommen. Patches, die eine Deny-List hinzufügen, sind ein Geschenk: Man liest die Liste und fragt dann, was IANA noch hat, das die Liste vergessen hat. Eine verwandte Suche behauptete, Go 1.26 IsGlobalUnicast hätte 0.0.0.1 geschlossen. Ich habe das Prädikat und die In-Tree-Tests geprüft. Sie decken 0.0.0.1 nie ab.
Bereits dokumentierte Irrwege: Webhook-Erstellung HTTP 201 als Oracle behandeln (die Historie ist das Oracle); erfolgreiche Zustellung an 127.0.0.1 (das wäre ein anderer Bug - das Lab erfordert, dass sie verweigert wird); /api/internal mit X-Forgejo-Internal-Auth ansprechen; eine Reverse Shell. Theater.
python3 forgejo-hostmatcher-0001-ssrf-Abraxas-Labs.py
oder:
cd lab
./run.sh
Ziel nur http://127.0.0.1:19320. Image codeberg.org/forgejo/forgejo:16.0.5. Oracle-Sidecar teilt den Forgejo-Netns auf Port 8080.
create-hook name=neg-loopback ...
negative-control 127.0.0.1 blocked
create-hook name=hit-0001 ...
poll id=2 witness=True
SUCCESS forgejo-hostmatcher-0001-ssrf how=live pin=v16.0.5 0.0.0.1:8080 HTTP 200 FORGEJO-HOSTMATCHER-0001-WITNESS
RFC 6890 0.0.0.0/8 als nicht-external behandeln (eine Reserved-Net-Liste, nicht IsGlobalUnicast). 0.0.0.1 muss genauso verweigert werden wie 127.0.0.1.
hostmatcher.goGNU Affero GPL v3.0. Siehe LICENSE. Nur Loopback-Lab. Keine Gewährleistung.