
Forgejo 16.0.5 - Alta - Remoto - SSRF - hostmatcher permite 0.0.0.1 como IP público
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · forgejo-hostmatcher-0001-ssrf
Classe: SSRF Alcance: Remoto
Forgejo 16.0.5 (8d24b7d) - Forgejo
O padrão [webhook] ALLOWED_HOST_LIST é vazio, então services/webhook/deliver.go constrói o cliente com o external embutido. Esse predicado é o IsGlobalUnicast() && !isPrivate() do Go. isPrivate é RFC1918/4193 mais a adição de CGNAT da v16.0.2. O 0.0.0.0/8 da RFC 6890 não é nenhum desses. 127.0.0.1 é loopback e é negado. 0.0.0.1 é permitido.
Um usuário autenticado pode fazer webhook para http://0.0.0.1 e ler o corpo HTTP do histórico de hooks. 127.0.0.1 na mesma porta é negado.
| ID | ainda sem CVE |
| Classe | SSRF |
| Alcance | Remoto (autenticado; qualquer usuário que possa criar um webhook de repositório) |
| 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 |
| Produto | Forgejo |
| Afetado | até v16.0.5 (8d24b7d); resquício da adição de CGNAT da v16.0.2 |
| Autenticação | usuário autenticado que possa criar um webhook de repositório |
| Licença | GNU Affero GPL v3.0 |
| Lab | apenas 127.0.0.1 |
Registre-se em uma instalação padrão, crie um repositório, defina um webhook do forgejo para http://0.0.0.1:<port>/..., teste-o e leia o corpo da resposta.
| Privilégio | Método / sink | Recon / integridade / disponibilidade | Comprovado em lab ou inferido |
|---|---|---|---|
Discar para esta rede 0.0.0.1 contornando a allow-list padrão de webhooks | POST /api/v1/repos/{owner}/{repo}/hooks e então POST .../hooks/{id}/tests com URL http://0.0.0.1:8080/ssrf | Recon (HTTP de saída a partir do host Forgejo) | Comprovado em lab. Histórico armazenou corpo HTTP 200 FORGEJO-HOSTMATCHER-0001-WITNESS |
| Ler a resposta HTTP completa de quem quer que tenha respondido | GET /{owner}/{repo}/settings/hooks/{id} imprime ResponseInfo.Body | Recon (SSRF de resposta completa) | Comprovado em lab. Mesmo witness na página de histórico |
Mesma classe via IPv4-mapeado [::ffff:0.0.0.1] | Mesmo matcher To4() e então checkIP | Recon | Inferido a partir do hostmatcher. O lab usou 0.0.0.1 |
| Alcançar um listener que se vinculou de forma mais ampla que loopback no netns do Forgejo | connect() do Linux para 0.0.0.0/8 é este-host uma vez que 0.0.0.1 está em lo | Recon | Comprovado em lab contra o oráculo sidecar. Não é uma afirmação sobre um IMDS de nuvem específico |
Fechados no mesmo matcher padrão: 127.0.0.1 (negativa comprovada em lab no histórico), 127.0.0.0/8, RFC1918, CGNAT 100.64.0.0/10, link-local 169.254.0.0/16 incluindo IPv4 IMDS, não especificado 0.0.0.0. O webhook de saída não ganha X-Forgejo-Internal-Auth / INTERNAL_TOKEN. Isto não é RCE por si só.
Esta não é a correção de CGNAT da v16.0.2. O Forgejo nunca desenvolveu uma lista de redes reservadas no estilo Gitea. Mesma classe que gitea-hostmatcher-0000-ssrf.
A v16.0.2 colocou CGNAT em private. Patches que adicionam uma deny-list são um presente: você lê a lista, depois pergunta o que a IANA ainda tem que a lista esqueceu. Uma caçada irmã afirmou que o IsGlobalUnicast do Go 1.26 fechava 0.0.0.1. Eu verifiquei o predicado e os testes na árvore. Eles nunca cobrem 0.0.0.1.
Caminhos errados já registrados: tratar o HTTP 201 de criação de webhook como o oráculo (o histórico é o oráculo); a entrega para 127.0.0.1 ter sucesso (isso seria um bug diferente - o lab exige que seja negada); atingir /api/internal com X-Forgejo-Internal-Auth; um reverse shell. Teatro.
python3 forgejo-hostmatcher-0001-ssrf-Abraxas-Labs.py
ou:
cd lab
./run.sh
Alvo apenas http://127.0.0.1:19320. Imagem codeberg.org/forgejo/forgejo:16.0.5. O sidecar oráculo compartilha o netns do Forgejo na 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
Trate o 0.0.0.0/8 da RFC 6890 como não-externo (uma lista de redes reservadas, não IsGlobalUnicast). 0.0.0.1 deve ser negado da mesma forma que 127.0.0.1 é.
hostmatcher.goGNU Affero GPL v3.0. Veja LICENSE. Apenas lab em loopback. Sem garantia.