
GitLab - High - 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.
Gitea import calls UrlBlocker.validate! once, then for https throws away the IP-pinned URI and hands Faraday the original hostname. Octokit UrlValidation validates again and does not rewrite env[:url]. Rebind the name between those lookups and GitLab GETs loopback as if it were Gitea. The repo-list JSON comes back on GET /import/gitea/status.json. HTTP imports keep the pin. Direct https://127.0.0.1 is blocked.
A signed-in user who can open Gitea import on their personal namespace can make GitLab fetch a host they rebind to loopback, and they get the body.
| ID | no CVE yet |
| 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 |
| Product | GitLab CE |
| Affected | 19.4.1-ce.0 (26212baacadb on the v19.4.1-ee tree) |
| Auth | authenticated user, Gitea import, personal namespace |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
Set Gitea Host URL to https://<name-they-control>/. UrlBlocker allows the first public A-record. HTTPS client_options keeps that hostname. Rebind to 127.0.0.1. Faraday GETs the catcher on the GitLab worker loopback. status.json provider_repos is whatever the catcher returned.
On all-in-one omnibus that is Workhorse, Puma, exporters, and anything else listening on the worker. Not host RCE. Not unauthenticated. HTTP Host URL stays on the pinned IP.
Same product, sibling leftovers: Gitaly fetch omits resolved_address, webhook 0.0.0.0/8.
I read the 23 September 2026 19.4.1 / 19.3.3 / 19.2.7 patch notes first, then the v19.4.1-ee tree. CI regex RCE, MR diff XSS, Workhorse file.path read are closed on this pin. Import SSRF was not finished.
Import::GiteaController#client_options is the leftover:
host: verified_url.scheme == 'https' ? provider_url : verified_url.to_s,
HTTP uses verified_url (IP). HTTPS uses provider_url (the name the user typed). LegacyGithubImport::Client builds Octokit api_endpoint from that host. Gitlab::Octokit::UrlValidation calls validate! then @app.call(env) with no rewrite.
I stood up stock gitlab/gitlab-ce:19.4.1-ce.0. Local-requests off. Lab CA in /etc/gitlab/trusted-certs because Faraday ssl.verify is true. DNS for gitea.lab: lookups 1-2 return 1.1.1.1, later lookups return 127.0.0.1. Catcher shares the GitLab netns on :443. GET /import/gitea/status.json returned provider_repos named GITLAB-GITEA-IMPORT-SSRF-WITNESS. Catcher log: GET /api/v1/user/repos peer=127.0.0.1 host=gitea.lab.
Direct https://127.0.0.1/ : "Specified URL cannot be used". HTTP http://gitea.lab/ stayed on the pin and did not return the witness.
Wrong turns already recorded: /users/sign_in with Accept: application/json is 404 (HTML is 200); seed password CveLabRoot9! rejected because it contains root; CA bind-mount ro is EROFS after reconfigure links the file; first-two-then-loopback on lookup 2 makes UrlValidation block before Faraday connects. Pairs mode (odd public, even loopback) is what landed. A reverse shell. Theatre. The oracle is the witness in status.json plus the catcher peer.
cd lab
./run.sh
Target only http://127.0.0.1:18400. GitLab CE wants several GB RAM. First boot is slow. Authorized lab only.
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
Use verified_url.to_s for https the same way HTTP already does. Rewrite env[:url] in Octokit UrlValidation to the pinned IP.
gitea_controller.rb · legacy_github_import/client.rb · octokit/url_validation.rbGNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.