
Forgejo 16.0.5 - Élevé - À distance - SSRF - hostmatcher autorise 0.0.0.1 comme IP publique
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · forgejo-hostmatcher-0001-ssrf
Classe : SSRF Portée : Distante
Forgejo 16.0.5 (8d24b7d) - Forgejo
La valeur par défaut de [webhook] ALLOWED_HOST_LIST est vide, donc services/webhook/deliver.go construit le client avec le prédicat intégré external. Ce prédicat est le IsGlobalUnicast() && !isPrivate() de Go. isPrivate correspond à RFC1918/4193 plus l'ajout CGNAT de la v16.0.2. Le 0.0.0.0/8 de la RFC 6890 n'appartient à aucune de ces catégories. 127.0.0.1 est loopback et refusé. 0.0.0.1 est autorisé.
Un utilisateur connecté peut déclencher un webhook vers http://0.0.0.1 et lire le corps HTTP depuis l'historique du hook. 127.0.0.1 sur le même port est refusé.
| ID | pas encore de CVE |
| Classe | SSRF |
| Portée | Distante (authentifié ; tout utilisateur pouvant créer un webhook de dépôt) |
| CWE | CWE-918 |
| CVSS | Élevé : 7.7 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N |
| Produit | Forgejo |
| Affecté | jusqu'à v16.0.5 (8d24b7d) ; résidu de l'ajout CGNAT de la v16.0.2 |
| Auth | utilisateur connecté pouvant créer un webhook de dépôt |
| Licence | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 uniquement |
S'inscrire sur une installation par défaut, créer un dépôt, configurer un webhook forgejo vers http://0.0.0.1:<port>/..., le tester, et lire le corps de la réponse.
| Privilège | Méthode / sink | Reconnaissance / intégrité / disponibilité | Prouvé en lab ou déduit |
|---|---|---|---|
Composer vers 0.0.0.1 de ce réseau en contournant la liste d'autorisation par défaut des webhooks | POST /api/v1/repos/{owner}/{repo}/hooks puis POST .../hooks/{id}/tests avec l'URL http://0.0.0.1:8080/ssrf | Reconnaissance (HTTP sortant depuis l'hôte Forgejo) | Prouvé en lab. Historique stocké : corps HTTP 200 FORGEJO-HOSTMATCHER-0001-WITNESS |
| Lire la réponse HTTP complète de ce qui a répondu | GET /{owner}/{repo}/settings/hooks/{id} affiche ResponseInfo.Body | Reconnaissance (SSRF à réponse complète) | Prouvé en lab. Même témoin dans la page d'historique |
Même classe via IPv4-mapped [::ffff:0.0.0.1] | Même matcher To4() puis checkIP | Reconnaissance | Déduit de hostmatcher. Le lab a utilisé 0.0.0.1 |
| Atteindre un listener lié plus largement que le loopback dans le netns Forgejo | Le connect() Linux vers 0.0.0.0/8 est cet-hôte une fois que 0.0.0.1 est sur lo | Reconnaissance | Prouvé en lab contre l'oracle sidecar. Pas une affirmation sur un IMDS cloud spécifique |
Fermé sur le même matcher par défaut : 127.0.0.1 (refus prouvé en lab dans l'historique), 127.0.0.0/8, RFC1918, CGNAT 100.64.0.0/10, link-local 169.254.0.0/16 y compris l'IMDS IPv4, non spécifié 0.0.0.0. Le webhook sortant ne gagne pas X-Forgejo-Internal-Auth / INTERNAL_TOKEN. Ce n'est pas du RCE en soi.
Ce n'est pas le correctif CGNAT de la v16.0.2. Forgejo n'a jamais développé de liste de réseaux réservés à la manière de Gitea. Même classe que gitea-hostmatcher-0000-ssrf.
La v16.0.2 a mis le CGNAT dans private. Les correctifs qui ajoutent une deny-list sont un cadeau : on lit la liste, puis on se demande ce que l'IANA possède encore et que la liste a oublié. Une chasse similaire prétendait que IsGlobalUnicast de Go 1.26 fermait 0.0.0.1. J'ai vérifié le prédicat et les tests dans l'arbre. Ils ne couvrent jamais 0.0.0.1.
Fausses pistes déjà consignées : traiter le HTTP 201 de création de webhook comme l'oracle (l'historique est l'oracle) ; la livraison vers 127.0.0.1 qui réussit (ce serait un autre bug - le lab exige qu'elle soit refusée) ; atteindre /api/internal avec X-Forgejo-Internal-Auth ; un reverse shell. Du théâtre.
python3 forgejo-hostmatcher-0001-ssrf-Abraxas-Labs.py
ou :
cd lab
./run.sh
Cible uniquement http://127.0.0.1:19320. Image codeberg.org/forgejo/forgejo:16.0.5. Le sidecar oracle partage le netns Forgejo sur le 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
Traiter le 0.0.0.0/8 de la RFC 6890 comme non-externe (une liste de réseaux réservés, pas IsGlobalUnicast). 0.0.0.1 doit être refusé de la même manière que 127.0.0.1.
hostmatcher.goGNU Affero GPL v3.0. Voir LICENSE. Lab loopback uniquement. Aucune garantie.