
Proof-of-Concept und Lab für einen GitLab-CE-Webhook-SSRF über den 0.0.0.0/8-This-Network-Bereich, der es einem Maintainer ermöglicht, Loopback zu erreichen und Antworten aus Hook-Logs zu lesen.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitlab-webhook-0net-ssrf
GitLab CE 19.4.1 - GitLab Inc.
Gitlab::HTTP_V2::UrlBlocker blockiert exakt 0.0.0.0, :: und 127.0.0.0/8. Der Rest von 0.0.0.0/8 wird nicht blockiert. Linux behandelt diesen Bereich als "dieses Netzwerk" und liefert 0.0.0.1 an den lokalen Stack aus. Ein Projekt-Maintainer kann einen Webhook unter http://0.0.0.1:<port>/ erstellen, während allow_local_requests_from_web_hooks_and_services deaktiviert ist. GitLab speichert 8 KB der Antwort in den Hook-Logs.
Ein Maintainer seines eigenen Projekts kann GitLab dazu bringen, per 0.0.0.1 ein POST an Loopback zu senden und den Body aus dem Hook-Log auszulesen.
| ID | noch keine CVE |
| CWE | CWE-918 |
| CVSS | High: 8.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N |
| Produkt | GitLab CE |
| Betroffen | 19.4.1-ce.0 (26212baacadb im v19.4.1-ee-Baum) |
| Auth | authentifizierter Maintainer (admin_web_hook) |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
POST /api/v4/projects/:id/hooks mit url=http://0.0.0.1:<port>/. Auslösen oder testen. response_body aus der Hook-Events-API auslesen. Bei All-in-One-Omnibus teilt sich Sidekiq den Container-Netns mit Puma, Workhorse und den Exportern. Der Maintainer von seinem Projekt sollte diese nicht auslesen können.
http://127.0.0.1/ und http://169.254.169.254/ bleiben blockiert. Dies ist kein IMDS über Link-Local. Es ist das Loch im "this-network"-Bereich.
Gleiches Produkt, verwandte Überbleibsel: Gitea HTTPS import pin drop, Gitaly fetch omits resolved_address.
Ich habe url_blocker.rb nach den SSRF-Fixes von 19.4.1 gelesen. validate_localhost prüft exakt "::" und "0.0.0.0" plus Socket.ip_address_list. validate_loopback prüft ipv4_loopback? (127.0.0.0/8) und IPv6-Loopback. Die Specs decken Oktal-/Hex-Aliase von 127.0.0.1 ab. 0.0.0.1 wird nie erwähnt.
Der Webhook ist die Body-Senke: WebHookService sendet POSTs und speichert 8 KB. Der Maintainer hat admin_web_hook.
Ich habe ein unverändertes gitlab/gitlab-ce:19.4.1-ce.0 hochgefahren. Local-Requests aus. Catcher innerhalb des GitLab-Netns auf 0.0.0.1:18080. Hook-Erstellung http://127.0.0.1:18080/ und http://169.254.169.254/ : 422 Invalid url given. Hook-Erstellung http://0.0.0.1:18080/ : 201. Test-Push-Events : 201. Hook-Events response_body enthält GITLAB-WEBHOOK-0NET-SSRF-WITNESS.
Bereits dokumentierte Irrwege: /users/sign_in mit Accept: application/json ist 404; Seed-Passwort mit root ist WeakPasswords; das Hinzufügen von 0.0.0.1 zu lo bringt es in Socket.ip_address_list, sodass validate_localhost das "Positive" mit 422 ablehnt; IP_FREEBIND plus ip route add local 0.0.0.1/32 dev lo bindet, ohne in die Adressliste aufgenommen zu werden; $ im Passwort in Compose erfordert $$. Eine Reverse Shell. Theater. Das Orakel ist 201 bei 0.0.0.1, 422 bei 127.0.0.1, der Witness im Log.
cd lab
./run.sh
Ziel nur http://127.0.0.1:18420. GitLab CE benötigt mehrere GB RAM. Nur autorisiertes Lab.
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
0.0.0.0/8 (und ::ffff:0.0.0.0/104) genauso ablehnen, wie 127.0.0.0/8 abgelehnt wird.
url_blocker.rb · web_hook_service.rb · project_hooks.rbGNU Affero GPL v3.0. Siehe LICENSE. Nur Loopback-Lab. Keine Gewährleistung.