
Preuve de concept et laboratoire pour une SSRF de webhook GitLab CE via la plage this-network 0.0.0.0/8, permettant à un Maintainer d'atteindre le loopback et de lire les réponses depuis les journaux de hook.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitlab-webhook-0net-ssrf
Write-up : https://abraxaslabs.tech/research/gitlab-webhook-0net-ssrf
GitLab CE 19.4.1 - GitLab Inc.
Gitlab::HTTP_V2::UrlBlocker bloque exactement 0.0.0.0, :: et 127.0.0.0/8. Il ne bloque pas le reste de 0.0.0.0/8. Linux traite cette plage comme « ce réseau » et livrera 0.0.0.1 à la pile locale. Un Maintainer de projet peut créer un webhook sur http://0.0.0.1:<port>/ alors que allow_local_requests_from_web_hooks_and_services est désactivé. GitLab stocke 8 Ko de la réponse dans les journaux des hooks.
Un Maintainer de son propre projet peut faire envoyer par GitLab une requête POST vers la boucle locale via 0.0.0.1 et récupérer le corps de la réponse depuis le journal des hooks.
| ID | pas encore de CVE |
| CWE | CWE-918 |
| CVSS | Élevé : 8.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N |
| Produit | GitLab CE |
| Affecté | 19.4.1-ce.0 (26212baacadb sur l'arbre v19.4.1-ee) |
| Auth | Maintainer authentifié (admin_web_hook) |
| Licence | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 uniquement |
POST /api/v4/projects/:id/hooks avec url=http://0.0.0.1:<port>/. Déclencher ou tester. Lire response_body depuis l'API des événements de hook. Sur un omnibus tout-en-un, Sidekiq partage le netns du conteneur avec Puma, Workhorse et les exporters. Un Maintainer de son projet ne devrait pas pouvoir les lire.
http://127.0.0.1/ et http://169.254.169.254/ restent bloqués. Il ne s'agit pas de l'IMDS via link-local. C'est le trou du « this-network ».
Même produit, résidus similaires : Gitea HTTPS import pin drop, Gitaly fetch omits resolved_address.
J'ai lu url_blocker.rb après les correctifs SSRF de la 19.4.1. validate_localhost correspond exactement à "::" et "0.0.0.0" plus Socket.ip_address_list. validate_loopback correspond à ipv4_loopback? (127.0.0.0/8) et à la boucle locale ipv6. Les specs couvrent les alias octaux/hexadécimaux de 127.0.0.1. Elles ne mentionnent jamais 0.0.0.1.
Le webhook est le réceptacle du corps : WebHookService envoie un POST et stocke 8 Ko. Le Maintainer dispose de admin_web_hook.
J'ai déployé l'image standard gitlab/gitlab-ce:19.4.1-ce.0. Requêtes locales désactivées. Un catcher à l'intérieur du netns GitLab sur 0.0.0.1:18080. Création de hook http://127.0.0.1:18080/ et http://169.254.169.254/ : 422 Invalid url given. Création de hook http://0.0.0.1:18080/ : 201. Test des événements push : 201. Le response_body des événements de hook contient GITLAB-WEBHOOK-0NET-SSRF-WITNESS.
Fausses pistes déjà consignées : /users/sign_in avec Accept: application/json renvoie 404 ; un mot de passe initial contenant root déclenche WeakPasswords ; ajouter 0.0.0.1 à lo le fait apparaître dans Socket.ip_address_list, donc validate_localhost renvoie 422 sur le « positif » ; IP_FREEBIND plus ip route add local 0.0.0.1/32 dev lo permet de se lier sans rejoindre la liste d'adresses ; un $ dans le mot de passe d'un compose nécessite $$. Un reverse shell. Du théâtre. L'oracle, c'est 201 sur 0.0.0.1, 422 sur 127.0.0.1, et le témoin dans le journal.
cd lab
./run.sh
Ciblez uniquement http://127.0.0.1:18420. GitLab CE nécessite plusieurs Go de RAM. Laboratoire autorisé uniquement.
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
Refuser 0.0.0.0/8 (et ::ffff:0.0.0.0/104) de la même manière que 127.0.0.0/8 est refusé.
url_blocker.rb · web_hook_service.rb · project_hooks.rbGNU Affero GPL v3.0. Voir LICENSE. Laboratoire en boucle locale uniquement. Aucune garantie.