
GitLab - Élevé - SSRF d'import HTTPS Gitea - Abandon du pin 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.
L'import Gitea appelle UrlBlocker.validate! une fois, puis pour https jette l'URI épinglée à l'IP et transmet à Faraday le nom d'hôte d'origine. La UrlValidation d'Octokit valide à nouveau et ne réécrit pas env[:url]. Rebind le nom entre ces deux résolutions et GitLab fait des GET sur le loopback comme s'il s'agissait de Gitea. Le JSON de la liste des dépôts revient sur GET /import/gitea/status.json. Les imports HTTP conservent l'épinglage. Un https://127.0.0.1 direct est bloqué.
Un utilisateur authentifié qui peut ouvrir l'import Gitea sur son namespace personnel peut faire récupérer par GitLab un hôte qu'il rebinde vers le loopback, et il obtient le corps de la réponse.
| 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é, import Gitea, namespace personnel |
| Licence | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 uniquement |
Définir l'URL de l'hôte Gitea sur https://<nom-qu-il-contrôle>/. UrlBlocker autorise le premier enregistrement A public. Les client_options HTTPS conservent ce nom d'hôte. Rebind vers 127.0.0.1. Faraday fait un GET sur le catcher via le loopback du worker GitLab. Le champ provider_repos de status.json contient ce que le catcher a renvoyé.
Sur une installation omnibus tout-en-un, cela inclut Workhorse, Puma, les exporters et tout autre service à l'écoute sur le worker. Ce n'est pas une RCE sur l'hôte. Ce n'est pas non authentifié. L'URL d'hôte HTTP reste sur l'IP épinglée.
Même produit, résidus similaires : Gitaly fetch omet resolved_address, webhook 0.0.0.0/8.
J'ai d'abord lu les notes de patch du 23 septembre 2026 pour 19.4.1 / 19.3.3 / 19.2.7, puis l'arbre v19.4.1-ee. La RCE par regex dans la CI, la XSS dans le diff de MR et la lecture de file.path par Workhorse sont corrigées sur ce pin. L'SSRF d'import n'était pas terminée.
Import::GiteaController#client_options est le résidu :
host: verified_url.scheme == 'https' ? provider_url : verified_url.to_s,
HTTP utilise verified_url (IP). HTTPS utilise provider_url (le nom saisi par l'utilisateur). LegacyGithubImport::Client construit l'api_endpoint Octokit à partir de cet hôte. Gitlab::Octokit::UrlValidation appelle validate! puis @app.call(env) sans réécriture.
J'ai déployé l'image standard gitlab/gitlab-ce:19.4.1-ce.0. Requêtes locales désactivées. CA du labo dans /etc/gitlab/trusted-certs car ssl.verify de Faraday vaut true. DNS pour gitea.lab : les résolutions 1-2 renvoient 1.1.1.1, les résolutions suivantes renvoient 127.0.0.1. Le catcher partage le netns de GitLab sur :443. GET /import/gitea/status.json a renvoyé provider_repos nommé GITLAB-GITEA-IMPORT-SSRF-WITNESS. Journal du catcher : GET /api/v1/user/repos peer=127.0.0.1 host=gitea.lab.
https://127.0.0.1/ direct : « Specified URL cannot be used ». HTTP http://gitea.lab/ est resté sur l'épinglage et n'a pas renvoyé le témoin.
Fausses pistes déjà consignées : /users/sign_in avec Accept: application/json renvoie 404 (le HTML renvoie 200) ; le mot de passe initial CveLabRoot9! est rejeté car il contient root ; le bind-mount du CA en ro donne EROFS après que la reconfiguration a lié le fichier ; les deux premières résolutions puis le loopback à la résolution 2 font que UrlValidation bloque avant que Faraday ne se connecte. C'est le mode paires (impair public, pair loopback) qui a fonctionné. Un reverse shell. Du théâtre. L'oracle est le témoin dans status.json plus le pair du catcher.
cd lab
./run.sh
Ciblez uniquement http://127.0.0.1:18400. GitLab CE nécessite plusieurs Go de RAM. Le premier démarrage est lent. Laboratoire autorisé uniquement.
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
Utilisez verified_url.to_s pour https de la même manière que HTTP le fait déjà. Réécrivez env[:url] dans UrlValidation d'Octokit vers l'IP épinglée.
gitea_controller.rb · legacy_github_import/client.rb · octokit/url_validation.rbGNU Affero GPL v3.0. Voir LICENSE. Laboratoire en boucle locale uniquement. Aucune garantie.