
Forgejo 16.0.5 - Alta - Remota - SSRF - hostmatcher consente 0.0.0.1 come IP pubblico
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · forgejo-hostmatcher-0001-ssrf
Classe: SSRF Portata: Remota
Forgejo 16.0.5 (8d24b7d) - Forgejo
Il valore predefinito di [webhook] ALLOWED_HOST_LIST è vuoto, quindi services/webhook/deliver.go costruisce il client con l'external integrato. Quel predicato è Go IsGlobalUnicast() && !isPrivate(). isPrivate è RFC1918/4193 più l'aggiunta CGNAT della v16.0.2. L'RFC 6890 0.0.0.0/8 non è nessuno di questi. 127.0.0.1 è loopback ed è negato. 0.0.0.1 è consentito.
Un utente autenticato può impostare un webhook verso http://0.0.0.1 e leggere il corpo HTTP dalla cronologia degli hook. 127.0.0.1 sulla stessa porta è negato.
| ID | nessuna CVE al momento |
| Classe | SSRF |
| Portata | Remota (autenticato; qualsiasi utente che può creare un webhook di repository) |
| CWE | CWE-918 |
| CVSS | Alto: 7.7 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N |
| Prodotto | Forgejo |
| Affetto | fino alla v16.0.5 (8d24b7d); residuo dell'aggiunta CGNAT della v16.0.2 |
| Auth | utente autenticato che può creare un webhook di repository |
| Licenza | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1 |
Registrarsi su un'installazione predefinita, creare un repository, impostare un webhook forgejo verso http://0.0.0.1:<port>/..., testarlo e leggere il corpo della risposta.
| Privilegio | Metodo / sink | Recon / integrità / disponibilità | Provato in lab o dedotto |
|---|---|---|---|
Connettere 0.0.0.1 di questa rete aggirando la allow-list predefinita dei webhook | POST /api/v1/repos/{owner}/{repo}/hooks poi POST .../hooks/{id}/tests con URL http://0.0.0.1:8080/ssrf | Recon (HTTP in uscita dall'host Forgejo) | Provato in lab. Cronologia memorizza corpo HTTP 200 FORGEJO-HOSTMATCHER-0001-WITNESS |
| Leggere l'intera risposta HTTP di qualunque cosa abbia risposto | GET /{owner}/{repo}/settings/hooks/{id} stampa ResponseInfo.Body | Recon (SSRF con risposta completa) | Provato in lab. Stesso witness nella pagina della cronologia |
Stessa classe tramite IPv4-mapped [::ffff:0.0.0.1] | Stesso matcher To4() poi checkIP | Recon | Dedotto da hostmatcher. Il lab ha usato 0.0.0.1 |
| Raggiungere un listener che si è bindato più ampiamente del loopback nel netns di Forgejo | La connect() di Linux verso 0.0.0.0/8 è this-host una volta che 0.0.0.1 è su lo | Recon | Provato in lab contro l'oracle sidecar. Non è un'affermazione su uno specifico IMDS cloud |
Chiusi sullo stesso matcher predefinito: 127.0.0.1 (negazione provata in lab nella cronologia), 127.0.0.0/8, RFC1918, CGNAT 100.64.0.0/10, link-local 169.254.0.0/16 incluso IPv4 IMDS, non specificato 0.0.0.0. Il webhook in uscita non ottiene X-Forgejo-Internal-Auth / INTERNAL_TOKEN. Questo non è RCE di per sé.
Questa non è la correzione CGNAT della v16.0.2. Forgejo non ha mai sviluppato una lista di reti riservate in stile Gitea. Stessa classe di gitea-hostmatcher-0000-ssrf.
La v16.0.2 ha inserito il CGNAT in private. Le patch che aggiungono una deny-list sono un regalo: leggi la lista, poi ti chiedi cosa l'IANA ha ancora che la lista ha dimenticato. Una caccia parallela sosteneva che IsGlobalUnicast di Go 1.26 chiudesse 0.0.0.1. Ho controllato il predicato e i test nel repository. Non coprono mai 0.0.0.1.
Vicoli ciechi già registrati: trattare l'HTTP 201 di creazione webhook come oracle (la cronologia è l'oracle); la consegna verso 127.0.0.1 che riesce (sarebbe un bug diverso - il lab richiede che sia negata); colpire /api/internal con X-Forgejo-Internal-Auth; una reverse shell. Teatro.
python3 forgejo-hostmatcher-0001-ssrf-Abraxas-Labs.py
oppure:
cd lab
./run.sh
Target solo http://127.0.0.1:19320. Immagine codeberg.org/forgejo/forgejo:16.0.5. L'oracle sidecar condivide il netns di Forgejo sulla porta 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
Trattare l'RFC 6890 0.0.0.0/8 come non-external (una lista di reti riservate, non IsGlobalUnicast). 0.0.0.1 deve essere negato allo stesso modo di 127.0.0.1.
hostmatcher.goGNU Affero GPL v3.0. Vedi LICENSE. Solo lab su loopback. Nessuna garanzia.