Preuve de concept et laboratoire pour une SSRF de fetch Gitaly dans GitLab CE 19.4.1 : les importateurs GitHub/Bitbucket ignorent l'épinglage de resolved_address, permettant à un utilisateur authentifié de rebinder le DNS et de récupérer des hôtes internes.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitlab-gitaly-fetch-ssrf
GitLab CE 19.4.1 - GitLab Inc.
L'import GitHub Enterprise / Bitbucket Server (et Direct Transfer, lorsque ce flag est activé) contourne Projects::ImportService#get_resolved_address. Repository#fetch_as_mirror utilise par défaut resolved_address: "". Gitaly FetchRemote / CreateRepositoryFromURL résout à nouveau le nom d'hôte du clone. UrlBlocker avait déjà autorisé un enregistrement A public. Rebind, et les objets git-HTTP atterrissent dans le projet importé.
Un utilisateur authentifié capable de lancer un import GitHub Enterprise peut faire en sorte que Gitaly git-fetch un hôte qui n'est plus l'IP épinglée par UrlBlocker.
| ID | pas encore de CVE |
| CWE | CWE-918, CWE-367 |
| CVSS | Élevé : 7.7 CVSS:3.1/AV:N/AC:H/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 | utilisateur authentifié, importateur GitHub Enterprise / Bitbucket Server |
| Licence | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 uniquement |
Pointer l'import GitHub Enterprise vers un nom d'hôte qu'il contrôle. Rails UrlBlocker.validate! autorise un enregistrement A public. ParallelImporter.imports_repository? est vrai, donc ImportService n'épingle jamais. RepositoryImporter#fetch_as_mirror appelle Gitaly avec un resolved_address vide. Gitaly fait un HTTP-fetch git de ce que ce nom est devenu. Le dépôt importé contient les objets.
Sur un omnibus tout-en-un, c'est la loopback du worker. Les noms d'hôte de clone directs http://127.0.0.1 échouent en mode fermé. L'import par URL de dépôt épingle déjà. Direct Transfer nécessite bulk_import_enabled (valeur par défaut auto-gérée : false).
Même produit, résidus similaires : Gitea HTTPS import pin drop, webhook 0.0.0.0/8.
J'ai lu les notes de patch 19.4.1, puis les importateurs. Projects::ImportService#get_resolved_address réécrit le nom d'hôte en l'IP validée et le transmet à Gitaly. Les importateurs parallèles GitHub et Bitbucket Server définissent imports_repository? et contournent ce chemin.
project.repository.fetch_as_mirror(project.unsafe_import_url, refmap: refmap, forced: true)
fetch_as_mirror utilise par défaut resolved_address: "". Le repository_pipeline.rb de Direct Transfer appelle même validate! et jette la valeur de retour.
J'ai monté un gitlab/gitlab-ce:19.4.1-ce.0 standard. Les import_sources de CE démarrent vides ; le lab active GitHub. POST /api/v4/import/github avec github_hostname=http://ghe.lab:18080. Un catcher dans le netns GitLab parle le faux JSON GHE plus git http-backend. Le blob témoin WITNESS du dépôt seed est GITLAB-GITALY-FETCH-SSRF-WITNESS.
GET /api/v4/projects/1/repository/files/WITNESS/raw?ref=master a renvoyé le témoin. Log Gitaly : FetchRemote grpc.code=OK sur root/ghe-ssrf. Catcher : git/2.55 info/refs et git-upload-pack. Le négatif github_hostname=http://127.0.0.1:18080 est 400 Invalid URL.
Fausses pistes déjà consignées : le seed CveLabRoot9! est un rejet WeakPasswords (sous-chaîne root) ; 19.4 complète au-delà de cette vérification à 64 caractères ; Faraday ne peut pas se connecter à une IP publique qui n'est pas le catcher, donc le harnais REDIRECT 1.1.1.1:18080 pendant qu'UrlBlocker voit toujours un A public ; git-HTTP peut gagner avant que GET /repos/org/repo ne bascule le DNS ; GitLab renvoie 502 en plein import et ensuite le fichier est là quand même. Un reverse shell. Du théâtre. L'oracle, c'est le blob importé plus le FetchRemote de Gitaly.
cd lab
./run.sh
Cible uniquement http://127.0.0.1:18410. GitLab CE demande plusieurs Go de RAM. Lab autorisé uniquement.
negative-loopback status=400 Invalid URL
witness-import status=201 import_status=scheduled
file-final http=200 body='GITLAB-GITALY-FETCH-SSRF-WITNESS'
SUCCESS GITLAB-GITALY-FETCH-SSRF imported-witness git-fetch-no-pin GITLAB-GITALY-FETCH-SSRF-WITNESS
def import_repository
project.ensure_repository
refmap = Gitlab::GithubImport.refmap
project.repository.fetch_as_mirror(project.unsafe_import_url, refmap: refmap, forced: true)
Passez get_resolved_address dans fetch_as_mirror comme le fait déjà l'import par URL de dépôt. Ne jetez pas non plus UrlBlocker.validate! dans Direct Transfer.
repository_importer.rb · repository.rb · import_service.rbGNU Affero GPL v3.0. Voir LICENSE. Lab loopback uniquement. Aucune garantie.