
GitLab - Alta - SSRF en importación HTTPS de Gitea - eliminación del pin de UrlBlocker
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · gitlab-gitea-import-ssrf
Write-up: https://abraxaslabs.tech/research/gitlab-gitea-import-ssrf
GitLab CE 19.4.1 - GitLab Inc.
La importación de Gitea llama a UrlBlocker.validate! una vez y, después, para https descarta la URI fijada a la IP y entrega a Faraday el nombre de host original. La UrlValidation de Octokit valida de nuevo y no reescribe env[:url]. Rebindea el nombre entre esas búsquedas y GitLab hará GET a loopback como si fuera Gitea. El JSON de la lista de repositorios vuelve en GET /import/gitea/status.json. Las importaciones HTTP conservan el pin. https://127.0.0.1 directo está bloqueado.
Un usuario autenticado que pueda abrir la importación de Gitea en su espacio de nombres personal puede hacer que GitLab obtenga un host que él rebindea a loopback, y recibe el cuerpo.
| ID | aún sin CVE |
| CWE | CWE-918, CWE-367 |
| CVSS | Alto: 7.7 CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N |
| Producto | GitLab CE |
| Afectado | 19.4.1-ce.0 (26212baacadb en el árbol v19.4.1-ee) |
| Autenticación | usuario autenticado, importación de Gitea, espacio de nombres personal |
| Licencia | GNU Affero GPL v3.0 |
| Laboratorio | solo 127.0.0.1 |
Establece la URL del host de Gitea en https://<nombre-que-controla>/. UrlBlocker permite el primer registro A público. Los client_options de HTTPS conservan ese nombre de host. Rebindea a 127.0.0.1. Faraday hace GET al catcher en el loopback del worker de GitLab. provider_repos en status.json es lo que haya devuelto el catcher.
En un omnibus todo-en-uno eso es Workhorse, Puma, los exportadores y cualquier otra cosa que escuche en el worker. No es RCE del host. No es sin autenticación. La URL de host HTTP permanece en la IP fijada.
Mismo producto, restos hermanos: la obtención de Gitaly omite resolved_address, webhook 0.0.0.0/8.
Primero leí las notas del parche del 23 de septiembre de 2026 para 19.4.1 / 19.3.3 / 19.2.7, y después el árbol v19.4.1-ee. El RCE por regex en CI, el XSS en el diff de MR y la lectura de file.path de Workhorse están cerrados en este pin. El SSRF de importación no estaba terminado.
Import::GiteaController#client_options es el resto:
host: verified_url.scheme == 'https' ? provider_url : verified_url.to_s,
HTTP usa verified_url (IP). HTTPS usa provider_url (el nombre que el usuario escribió). LegacyGithubImport::Client construye el api_endpoint de Octokit a partir de ese host. Gitlab::Octokit::UrlValidation llama a validate! y luego a @app.call(env) sin reescritura.
Levanté la imagen estándar gitlab/gitlab-ce:19.4.1-ce.0. Local-requests desactivado. CA del laboratorio en /etc/gitlab/trusted-certs porque ssl.verify de Faraday es true. DNS para gitea.lab: las consultas 1-2 devuelven 1.1.1.1, las consultas posteriores devuelven 127.0.0.1. El catcher comparte el netns de GitLab en :443. GET /import/gitea/status.json devolvió provider_repos con el nombre GITLAB-GITEA-IMPORT-SSRF-WITNESS. Registro del catcher: GET /api/v1/user/repos peer=127.0.0.1 host=gitea.lab.
Directo a https://127.0.0.1/ : "Specified URL cannot be used". HTTP http://gitea.lab/ se mantuvo en el pin y no devolvió el witness.
Errores ya registrados: /users/sign_in con Accept: application/json da 404 (el HTML da 200); la contraseña semilla CveLabRoot9! fue rechazada porque contiene root; el bind-mount de la CA en ro da EROFS después de que la reconfiguración enlaza el archivo; primero-dos-luego-loopback en la consulta 2 hace que UrlValidation bloquee antes de que Faraday conecte. El modo de pares (impar público, par loopback) es el que funcionó. Una reverse shell. Teatro. El oráculo es el witness en status.json más el peer del catcher.
cd lab
./run.sh
Dirígete solo a http://127.0.0.1:18400. GitLab CE requiere varios GB de RAM. El primer arranque es lento. Solo para laboratorio autorizado.
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
Usa verified_url.to_s para https de la misma manera que ya lo hace HTTP. Reescribe env[:url] en UrlValidation de Octokit a la IP fijada.
gitea_controller.rb · legacy_github_import/client.rb · octokit/url_validation.rbGNU Affero GPL v3.0. Consulta LICENSE. Solo para laboratorio en loopback. Sin garantía.