
Prueba de concepto y laboratorio para un SSRF de webhook de GitLab CE a través del rango de esta red 0.0.0.0/8, que permite a un Maintainer alcanzar el loopback y leer respuestas desde los registros de hooks.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitlab-webhook-0net-ssrf
GitLab CE 19.4.1 - GitLab Inc.
Gitlab::HTTP_V2::UrlBlocker bloquea exactamente 0.0.0.0, :: y 127.0.0.0/8. No bloquea el resto de 0.0.0.0/8. Linux trata ese rango como "esta red" y entregará 0.0.0.1 a la pila local. Un Maintainer de un proyecto puede crear un webhook en http://0.0.0.1:<port>/ mientras allow_local_requests_from_web_hooks_and_services está desactivado. GitLab almacena 8 KB de la respuesta en los logs del hook.
Un Maintainer de su propio proyecto puede hacer que GitLab haga POST a loopback mediante 0.0.0.1 y leer el cuerpo de vuelta desde el log del hook.
| ID | sin CVE todavía |
| 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 |
| Producto | GitLab CE |
| Afectado | 19.4.1-ce.0 (26212baacadb en el árbol v19.4.1-ee) |
| Auth | Maintainer autenticado (admin_web_hook) |
| Licencia | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1 |
POST /api/v4/projects/:id/hooks con url=http://0.0.0.1:<port>/. Disparar o probar. Leer response_body desde la API de eventos del hook. En omnibus todo-en-uno, Sidekiq comparte el netns del contenedor con Puma, Workhorse y los exporters. El Maintainer de su proyecto no debería leer eso.
http://127.0.0.1/ y http://169.254.169.254/ permanecen bloqueados. Esto no es IMDS vía link-local. Es el agujero de esta-red.
Mismo producto, restos hermanos: Gitea HTTPS import pin drop, Gitaly fetch omits resolved_address.
Leí url_blocker.rb después de los arreglos de SSRF de 19.4.1. validate_localhost es exactamente "::" y "0.0.0.0" más Socket.ip_address_list. validate_loopback es ipv4_loopback? (127.0.0.0/8) y loopback de ipv6. Las specs cubren alias octales/hex de 127.0.0.1. Nunca mencionan 0.0.0.1.
El webhook es el sumidero del cuerpo: WebHookService hace POST y almacena 8 KB. El Maintainer tiene admin_web_hook.
Levanté el gitlab/gitlab-ce:19.4.1-ce.0 estándar. Local-requests desactivado. Catcher dentro del netns de GitLab en 0.0.0.1:18080. Creación de hook http://127.0.0.1:18080/ y http://169.254.169.254/ : 422 Invalid url given. Creación de hook http://0.0.0.1:18080/ : 201. Test push events : 201. Los eventos del hook response_body contienen GITLAB-WEBHOOK-0NET-SSRF-WITNESS.
Caminos equivocados ya registrados: /users/sign_in con Accept: application/json es 404; contraseña semilla que contiene root es WeakPasswords; añadir 0.0.0.1 a lo lo pone en Socket.ip_address_list así que validate_localhost da 422 al "positivo"; IP_FREEBIND más ip route add local 0.0.0.1/32 dev lo enlaza sin unirse a la lista de direcciones; el $ en la contraseña de compose necesita $$. Una reverse shell. Teatro. El oráculo es 201 en 0.0.0.1, 422 en 127.0.0.1, testigo en el log.
cd lab
./run.sh
Objetivo solo http://127.0.0.1:18420. GitLab CE quiere varios GB de RAM. Solo 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
Denegar 0.0.0.0/8 (y ::ffff:0.0.0.0/104) de la misma forma en que se deniega 127.0.0.0/8.
url_blocker.rb · web_hook_service.rb · project_hooks.rbGNU Affero GPL v3.0. Ver LICENSE. Solo lab de loopback. Sin garantía.