Proof-of-Concept und Lab für eine GitLab CE 19.4.1 Gitaly-Fetch-SSRF: GitHub-/Bitbucket-Importer überspringen das Pinning von resolved_address, wodurch ein authentifizierter Benutzer DNS-Rebinding durchführen und interne Hosts abrufen kann.
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitlab-gitaly-fetch-ssrf
GitLab CE 19.4.1 - GitLab Inc.
GitHub Enterprise / Bitbucket Server Import (und Direct Transfer, wenn dieses Flag aktiviert ist) überspringen Projects::ImportService#get_resolved_address. Repository#fetch_as_mirror setzt standardmäßig resolved_address: "". Gitaly FetchRemote / CreateRepositoryFromURL löst den Clone-Hostnamen erneut auf. UrlBlocker hatte bereits einen öffentlichen A-Record zugelassen. Rebind, und git-HTTP-Objekte landen im importierten Projekt.
Ein angemeldeter Benutzer, der den GitHub-Enterprise-Import ausführen kann, kann Gitaly dazu bringen, einen Host per git-fetch abzurufen, der nicht mehr die von UrlBlocker gepinnte IP ist.
| ID | noch keine CVE |
| CWE | CWE-918, CWE-367 |
| CVSS | High: 7.7 CVSS:3.1/AV:N/AC:H/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 Benutzer, GitHub Enterprise / Bitbucket Server Importer |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Richte den GitHub-Enterprise-Import auf einen Hostnamen, den er kontrolliert. Rails UrlBlocker.validate! erlaubt einen öffentlichen A-Record. ParallelImporter.imports_repository? ist true, also pinnt ImportService nie. RepositoryImporter#fetch_as_mirror ruft Gitaly mit einem leeren resolved_address auf. Gitaly führt einen HTTP-Fetch per git auf den Namen aus, der jetzt gilt. Das importierte Repo enthält die Objekte.
Bei All-in-One-Omnibus ist das der Worker-Loopback. Direkte http://127.0.0.1-Clone-Hostnamen schlagen fail-closed fehl. Repository-by-URL-Import pinnt bereits. Direct Transfer benötigt bulk_import_enabled (self-managed Standard false).
Gleiches Produkt, verwandte Überbleibsel: Gitea HTTPS Import Pin Drop, Webhook 0.0.0.0/8.
Ich las die 19.4.1-Patchnotes und dann die Importer. Projects::ImportService#get_resolved_address schreibt den Hostnamen in die validierte IP um und übergibt sie an Gitaly. Die parallelen GitHub- und Bitbucket-Server-Importer setzen imports_repository? und überspringen diesen Pfad.
project.repository.fetch_as_mirror(project.unsafe_import_url, refmap: refmap, forced: true)
fetch_as_mirror setzt standardmäßig resolved_address: "". Direct Transfers repository_pipeline.rb ruft sogar validate! auf und verwirft den Rückgabewert.
Ich startete das unveränderte gitlab/gitlab-ce:19.4.1-ce.0. CE import_sources startet leer; das Lab aktiviert GitHub. POST /api/v4/import/github mit github_hostname=http://ghe.lab:18080. Der Catcher im GitLab-Netns spricht gefälschtes GHE-JSON plus git http-backend. Der Seed-Repo-Blob WITNESS ist GITLAB-GITALY-FETCH-SSRF-WITNESS.
GET /api/v4/projects/1/repository/files/WITNESS/raw?ref=master gab den Witness zurück. Gitaly-Log: FetchRemote grpc.code=OK auf root/ghe-ssrf. Catcher: git/2.55 info/refs und git-upload-pack. Negatives github_hostname=http://127.0.0.1:18080 ist 400 Invalid URL.
Bereits dokumentierte Irrwege: Seed CveLabRoot9! ist ein WeakPasswords-Reject (root-Substring); 19.4 füllt über diese Prüfung hinaus auf 64 Zeichen auf; Faraday kann keine Verbindung zu einer öffentlichen IP herstellen, die nicht der Catcher ist, daher leitet das Harness 1.1.1.1:18080 per REDIRECT um, während UrlBlocker weiterhin ein öffentliches A sieht; git-HTTP kann gewinnen, bevor GET /repos/org/repo DNS umschaltet; GitLab antwortet mitten im Import mit 502 und danach ist die Datei trotzdem da. Eine Reverse Shell. Theater. Das Orakel ist der importierte Blob plus Gitaly FetchRemote.
cd lab
./run.sh
Ziel nur http://127.0.0.1:18410. GitLab CE benötigt mehrere GB RAM. Nur autorisiertes Lab.
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)
Übergib get_resolved_address an fetch_as_mirror, so wie es der Repository-by-URL-Import bereits tut. Verwirf auch bei Direct Transfer nicht UrlBlocker.validate!.
repository_importer.rb · repository.rb · import_service.rbGNU Affero GPL v3.0. Siehe LICENSE. Nur Loopback-Lab. Keine Gewährleistung.