
Prova de conceito e laboratório para um SSRF de webhook do GitLab CE através do intervalo de rede 0.0.0.0/8, permitindo que um Maintainer alcance o loopback e leia respostas dos logs de hook.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitlab-webhook-0net-ssrf
GitLab CE 19.4.1 - GitLab Inc.
Gitlab::HTTP_V2::UrlBlocker bloqueia exatamente 0.0.0.0, :: e 127.0.0.0/8. Ele não bloqueia o restante de 0.0.0.0/8. O Linux trata essa faixa como "esta rede" e entregará 0.0.0.1 à pilha local. Um Maintainer de projeto pode criar um webhook em http://0.0.0.1:<port>/ enquanto allow_local_requests_from_web_hooks_and_services está desativado. O GitLab armazena 8 KB da resposta nos logs do hook.
Um Maintainer do próprio projeto pode fazer o GitLab enviar POST para o loopback via 0.0.0.1 e ler o corpo de volta a partir do log do hook.
| ID | sem CVE ainda |
| CWE | CWE-918 |
| CVSS | Alto: 8.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N |
| Produto | GitLab CE |
| Afetado | 19.4.1-ce.0 (26212baacadb na árvore v19.4.1-ee) |
| Autenticação | Maintainer autenticado (admin_web_hook) |
| Licença | GNU Affero GPL v3.0 |
| Lab | somente 127.0.0.1 |
POST /api/v4/projects/:id/hooks com url=http://0.0.0.1:<port>/. Disparar ou testar. Ler response_body da API de eventos do hook. Em omnibus all-in-one, o Sidekiq compartilha o netns do contêiner com Puma, Workhorse e exporters. O Maintainer do projeto dele não deveria ler isso.
http://127.0.0.1/ e http://169.254.169.254/ permanecem bloqueados. Isto não é IMDS via link-local. É o furo da this-network.
Mesmo produto, resquícios irmãos: Gitea HTTPS import pin drop, Gitaly fetch omits resolved_address.
Li url_blocker.rb após as correções de SSRF da 19.4.1. validate_localhost é exatamente "::" e "0.0.0.0" mais Socket.ip_address_list. validate_loopback é ipv4_loopback? (127.0.0.0/8) e loopback ipv6. As specs cobrem aliases octal/hex de 127.0.0.1. Elas nunca mencionam 0.0.0.1.
O webhook é o sumidouro do corpo: WebHookService faz POST e armazena 8 KB. O Maintainer tem admin_web_hook.
Subi o gitlab/gitlab-ce:19.4.1-ce.0 padrão. Local-requests desativado. Catcher dentro do netns do GitLab em 0.0.0.1:18080. Criação de hook http://127.0.0.1:18080/ e http://169.254.169.254/ : 422 Invalid url given. Criação de hook http://0.0.0.1:18080/ : 201. Testar eventos de push : 201. O response_body dos eventos do hook contém GITLAB-WEBHOOK-0NET-SSRF-WITNESS.
Caminhos errados já registrados: /users/sign_in com Accept: application/json é 404; senha de seed contendo root é WeakPasswords; adicionar 0.0.0.1 a lo o coloca em Socket.ip_address_list, então validate_localhost retorna 422 no "positivo"; IP_FREEBIND mais ip route add local 0.0.0.1/32 dev lo vincula sem entrar na lista de endereços; $ no compose na senha precisa de $$. Um reverse shell. Teatro. O oráculo é 201 em 0.0.0.1, 422 em 127.0.0.1, testemunha no log.
cd lab
./run.sh
Alvo somente http://127.0.0.1:18420. O GitLab CE quer vários GB de RAM. Somente lab autorizado.
negative-a 127.0.0.1 blocked
negative-b link-local blocked
positive-hook id=1
SUCCESS GITLAB-WEBHOOK-0NET-SSRF webhook response_body from 0.0.0.1:18080 hook_id=1 GITLAB-WEBHOOK-0NET-SSRF-WITNESS
def validate_localhost(addrs_info)
local_ips = ["::", "0.0.0.0"]
local_ips.concat(Socket.ip_address_list.map(&:ip_address))
return if (local_ips & addrs_info.map(&:ip_address)).empty?
raise BlockedUrlError, "Requests to localhost are not allowed"
end
def validate_loopback(addrs_info)
return unless addrs_info.any? { |addr| addr.ipv4_loopback? || addr.ipv6_loopback? }
raise BlockedUrlError, "Requests to loopback addresses are not allowed"
end
Negue 0.0.0.0/8 (e ::ffff:0.0.0.0/104) da mesma forma que 127.0.0.0/8 é negado.
url_blocker.rb · web_hook_service.rb · project_hooks.rbGNU Affero GPL v3.0. Veja LICENSE. Somente lab em loopback. Sem garantia.