
GitLab – Hoch – Gitea HTTPS-Import SSRF – UrlBlocker Pin Drop
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitlab-gitea-import-ssrf
GitLab CE 19.4.1 - GitLab Inc.
Der Gitea-Import ruft UrlBlocker.validate! einmal auf, verwirft dann für https die an die IP gebundene URI und übergibt Faraday den ursprünglichen Hostnamen. Octokits UrlValidation validiert erneut und schreibt env[:url] nicht um. Bindet man den Namen zwischen diesen Lookups neu, holt GitLab Loopback ab, als wäre es Gitea. Das Repo-Listen-JSON kommt über GET /import/gitea/status.json zurück. HTTP-Importe behalten die Bindung. Direktes https://127.0.0.1 wird blockiert.
Ein angemeldeter Benutzer, der den Gitea-Import in seinem persönlichen Namespace öffnen kann, kann GitLab dazu bringen, einen Host abzurufen, den er auf Loopback umbindet, und er erhält den Body.
| ID | noch keine CVE |
| CWE | CWE-918, CWE-367 |
| CVSS | Hoch: 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, Gitea-Import, persönlicher Namespace |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Gitea-Host-URL auf https://<name-they-control>/ setzen. UrlBlocker erlaubt den ersten öffentlichen A-Record. HTTPS-client_options behält diesen Hostnamen. Auf 127.0.0.1 umbinden. Faraday ruft den Catcher auf dem Loopback des GitLab-Workers ab. status.json provider_repos ist, was auch immer der Catcher zurückgegeben hat.
Bei einem All-in-One-Omnibus sind das Workhorse, Puma, Exporter und alles andere, was auf dem Worker lauscht. Kein Host-RCE. Nicht unauthentifiziert. Die HTTP-Host-URL bleibt auf der gebundenen IP.
Gleiches Produkt, verwandte Überbleibsel: Gitaly-Fetch lässt resolved_address weg, Webhook 0.0.0.0/8.
Ich habe zuerst die Patch-Notes vom 23. September 2026 für 19.4.1 / 19.3.3 / 19.2.7 gelesen, dann den v19.4.1-ee-Baum. CI-Regex-RCE, MR-Diff-XSS, Workhorse-file.path-Read sind bei diesem Pin geschlossen. Import-SSRF war nicht fertig.
Import::GiteaController#client_options ist das Überbleibsel:
host: verified_url.scheme == 'https' ? provider_url : verified_url.to_s,
HTTP verwendet verified_url (IP). HTTPS verwendet provider_url (den Namen, den der Benutzer eingegeben hat). LegacyGithubImport::Client baut daraus den Octokit-api_endpoint. Gitlab::Octokit::UrlValidation ruft validate! auf und dann @app.call(env) ohne Umschreiben.
Ich habe ein Standard-gitlab/gitlab-ce:19.4.1-ce.0 hochgefahren. Local-Requests aus. Lab-CA in /etc/gitlab/trusted-certs, weil Faraday ssl.verify true ist. DNS für gitea.lab: Lookups 1-2 liefern 1.1.1.1, spätere Lookups liefern 127.0.0.1. Der Catcher teilt sich den GitLab-Netns auf :443. GET /import/gitea/status.json lieferte provider_repos mit dem Namen GITLAB-GITEA-IMPORT-SSRF-WITNESS. Catcher-Log: GET /api/v1/user/repos peer=127.0.0.1 host=gitea.lab.
Direktes https://127.0.0.1/ : "Specified URL cannot be used". HTTP http://gitea.lab/ blieb auf der Bindung und lieferte den Witness nicht.
Bereits dokumentierte Irrwege: /users/sign_in mit Accept: application/json ist 404 (HTML ist 200); Seed-Passwort CveLabRoot9! abgelehnt, weil es root enthält; CA-Bind-Mount ro ist EROFS, nachdem reconfigure die Datei verlinkt; erst-zwei-dann-Loopback bei Lookup 2 lässt UrlValidation blockieren, bevor Faraday sich verbindet. Pairs-Modus (ungerade öffentlich, gerade Loopback) war das, was funktionierte. Eine Reverse Shell. Theater. Das Orakel ist der Witness in status.json plus der Catcher-Peer.
cd lab
./run.sh
Ziel nur http://127.0.0.1:18400. GitLab CE will mehrere GB RAM. Der erste Boot ist langsam. Nur autorisiertes Lab.
status.json status=200 ... provider_repos ... GITLAB-GITEA-IMPORT-SSRF-WITNESS
catcher GET /api/v1/user/repos peer=127.0.0.1 host=gitea.lab status=200 witness=True
negative-a https://127.0.0.1 blocked
negative-b http://gitea.lab no-witness
SUCCESS GITLAB-GITEA-IMPORT-SSRF status.json mode=pairs http=200 GITLAB-GITEA-IMPORT-SSRF-WITNESS
def client_options
verified_url, provider_hostname = verify_blocked_uri
{
host: verified_url.scheme == 'https' ? provider_url : verified_url.to_s,
api_version: 'v1',
hostname: provider_hostname
}
end
Verwende verified_url.to_s für https genauso, wie HTTP es bereits tut. Schreibe env[:url] in Octokits UrlValidation auf die gebundene IP um.
gitea_controller.rb · legacy_github_import/client.rb · octokit/url_validation.rbGNU Affero GPL v3.0. Siehe LICENSE. Nur Loopback-Lab. Keine Gewährleistung.