
Forgejo 16.0.5 - Alta - Remota - SSRF - hostmatcher permite 0.0.0.1 como IP pública
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · forgejo-hostmatcher-0001-ssrf
Clase: SSRF Alcance: Remoto
Forgejo 16.0.5 (8d24b7d) - Forgejo
El valor predeterminado de [webhook] ALLOWED_HOST_LIST está vacío, por lo que services/webhook/deliver.go construye el cliente con el external integrado. Ese predicado es Go IsGlobalUnicast() && !isPrivate(). isPrivate es RFC1918/4193 más el añadido de CGNAT de la v16.0.2. El 0.0.0.0/8 de la RFC 6890 no es ninguno de esos. 127.0.0.1 es loopback y está denegado. 0.0.0.1 está permitido.
Un usuario autenticado puede hacer webhook a http://0.0.0.1 y leer el cuerpo HTTP del historial del hook. 127.0.0.1 en el mismo puerto está denegado.
| ID | sin CVE todavía |
| Clase | SSRF |
| Alcance | Remoto (autenticado; cualquier usuario que pueda crear un webhook de repositorio) |
| 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 |
| Producto | Forgejo |
| Afectado | hasta v16.0.5 (8d24b7d); residuo del añadido de CGNAT de la v16.0.2 |
| Auth | usuario autenticado que pueda crear un webhook de repositorio |
| Licencia | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1 |
Registrarse en una instalación predeterminada, crear un repositorio, configurar un webhook de forgejo a http://0.0.0.1:<port>/..., probarlo y leer el cuerpo de la respuesta.
| Privilegio | Método / sink | Recon / integridad / disponibilidad | Probado en lab o inferido |
|---|---|---|---|
Marcar esta red 0.0.0.1 saltándose la lista de permitidos predeterminada de webhooks | POST /api/v1/repos/{owner}/{repo}/hooks y luego POST .../hooks/{id}/tests con URL http://0.0.0.1:8080/ssrf | Recon (HTTP saliente desde el host de Forgejo) | Probado en lab. Historial almacenó cuerpo HTTP 200 FORGEJO-HOSTMATCHER-0001-WITNESS |
| Leer la respuesta HTTP completa de lo que sea que respondió | GET /{owner}/{repo}/settings/hooks/{id} imprime ResponseInfo.Body | Recon (SSRF de respuesta completa) | Probado en lab. Mismo testigo en la página de historial |
Misma clase vía IPv4-mapped [::ffff:0.0.0.1] | Mismo matcher To4() y luego checkIP | Recon | Inferido de hostmatcher. El lab usó 0.0.0.1 |
| Alcanzar un listener que se vinculó más ampliamente que loopback en el netns de Forgejo | El connect() de Linux a 0.0.0.0/8 es este-host una vez que 0.0.0.1 está en lo | Recon | Probado en lab contra el oráculo sidecar. No es una afirmación sobre un IMDS de nube específico |
Cerrado en el mismo matcher predeterminado: 127.0.0.1 (denegación probada en lab en el historial), 127.0.0.0/8, RFC1918, CGNAT 100.64.0.0/10, link-local 169.254.0.0/16 incluyendo IPv4 IMDS, no especificado 0.0.0.0. El webhook saliente no obtiene X-Forgejo-Internal-Auth / INTERNAL_TOKEN. Esto no es RCE por sí mismo.
Esto no es el arreglo de CGNAT de la v16.0.2. Forgejo nunca desarrolló una lista de redes reservadas al estilo Gitea. Misma clase que gitea-hostmatcher-0000-ssrf.
La v16.0.2 puso CGNAT en private. Los parches que añaden una deny-list son un regalo: lees la lista, y luego preguntas qué más tiene la IANA que la lista olvidó. Una búsqueda hermana afirmaba que IsGlobalUnicast de Go 1.26 cerraba 0.0.0.1. Revisé el predicado y los tests en el árbol. Nunca cubren 0.0.0.1.
Caminos equivocados ya registrados: tratar el HTTP 201 de creación de webhook como el oráculo (el historial es el oráculo); que la entrega a 127.0.0.1 tuviera éxito (eso sería un bug diferente - el lab requiere que esté denegado); golpear /api/internal con X-Forgejo-Internal-Auth; una reverse shell. Teatro.
python3 forgejo-hostmatcher-0001-ssrf-Abraxas-Labs.py
o:
cd lab
./run.sh
Objetivo solo http://127.0.0.1:19320. Imagen codeberg.org/forgejo/forgejo:16.0.5. El sidecar oráculo comparte el netns de Forgejo en el puerto 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
Tratar el 0.0.0.0/8 de la RFC 6890 como no-externo (una lista de redes reservadas, no IsGlobalUnicast). A 0.0.0.1 se le debe denegar de la misma manera que a 127.0.0.1.
hostmatcher.goGNU Affero GPL v3.0. Ver LICENSE. Solo lab de loopback. Sin garantía.