1 points par GN⁺ 6 시간 전 | 1 commentaires | Partager sur WhatsApp
  • Dans Git, -- n’est pas un terminateur d’options générique : il sépare les révisions des spécifications de chemin. Pour transmettre en toute sécurité une révision non fiable, il faut donc utiliser --end-of-options, pris en charge depuis Git 2.24.0
  • Dans git log --end-of-options "$rev" -- "$path", le premier marqueur sépare les options et la révision, tandis que le second -- sépare la révision et le chemin ; ils ne sont pas interchangeables
  • Même sans shell, en exécutant directement un tableau argv, si une entrée commençant par un tiret est interprétée comme une option telle que --upload-pack, core.sshCommand ou ProxyCommand, cela peut provoquer une injection d’argument CWE-88
  • Sur 19 gestionnaires de paquets examinés, 17 exécutent le binaire Git comme méthode par défaut ou unique, mais le seul outil à utiliser --end-of-options était cmd/go de Go
  • Les corrections de fond imposent un coût de compatibilité, car elles nécessitent de relever la version minimale de Git à 2.24.0, 2.30.0 ou 2.43.1 selon les sous-commandes ; les bibliothèques Git suppriment la frontière d’injection d’arguments, mais doivent suivre elles-mêmes les correctifs de sécurité d’upstream pour les checkout

Différence entre -- et --end-of-options

  • Dans les outils Unix classiques, -- marque la fin de l’interprétation des options ; ainsi rm -- -f traite -f comme un nom de fichier, et non comme l’option de suppression forcée
  • Git utilise depuis longtemps -- comme séparateur entre révisions et spécifications de chemin (pathspec)
    • git log foo est ambigu : s’agit-il d’une branche nommée foo ou d’un fichier ?
    • git log main -- README.md désigne les commits de main qui ont modifié README.md
  • À cause de cette conception, il n’existait pas de marqueur de fin d’options à l’emplacement des révisions ; dans git log "$rev", si $rev commence par un tiret, Git l’interprète comme une option
  • Le commit qui a introduit --end-of-options indique qu’un marqueur distinct était nécessaire pour séparer les options des révisions, car le -- existant servait déjà à distinguer révisions et spécifications de chemin
  • --end-of-options est documenté dans gitcli(7) et a été ajouté en novembre 2019 avec la sortie de Git 2.24.0

Utilisation correcte selon les commandes

  • git clone -- "$url" suit la convention POSIX pour clone : le -- placé avant l’URL met fin à l’interprétation des options
  • Le -- final dans git checkout "$ref" -- indique que $ref est une révision et non un nom de fichier, mais il n’empêche pas $ref d’être d’abord interprété comme une option
  • Pour transmettre en toute sécurité une révision et un chemin non fiables, il faut utiliser les deux marqueurs, comme dans git log --end-of-options "$rev" -- "$path"
    • --end-of-options sépare les options et la révision
    • -- sépare la révision et le chemin
  • Si l’on considère ces deux marqueurs comme interchangeables, on ne bloque pas les entrées qui commencent par un tiret

Une prise en charge différente selon les sous-commandes

  • La prise en charge de --end-of-options n’a pas été appliquée à toutes les commandes Git d’un coup : elle a été ajoutée par sous-commande
  • git rev-parse, qui utilise son propre parseur d’arguments, n’a commencé à le prendre en charge qu’un an après l’introduction initiale, avec Git 2.30.0
  • git checkout et git reset interprètent eux-mêmes -- ; l’implémentation initiale laissait --end-of-options dans la liste d’arguments, ce qui les amenait à le rejeter
    • Ce problème a été corrigé en février 2024, avec la sortie de Git 2.43.1

L’injection d’arguments peut survenir même sans shell

  • Git, Mercurial et SSH proposent officiellement des options permettant d’exécuter une commande spécifiée par l’appelant
    • git clone --upload-pack=<cmd> spécifie le binaire côté serveur
    • -c core.sshCommand=<cmd> sur n’importe quel appel Git modifie la commande de connexion
    • --config=alias.<subcmd>=!<shell> dans Mercurial redéfinit la sous-commande à exécuter comme script shell arbitraire
    • -oProxyCommand=<cmd> dans SSH spécifie la commande proxy
  • Si un programme wrapper place une chaîne non fiable dans la liste d’arguments, ces fonctionnalités peuvent devenir un vecteur d’attaque
  • Ce type d’échec relève de CWE-88, l’injection d’argument (argument injection), et diffère de l’injection de commandes shell
    • Il peut survenir même si le programme utilise un tableau argv et exec au lieu de system()
    • Le tableau est transmis à Git sans altération, mais Git interprète les arguments commençant par un tiret comme des options
  • CVE-2019-13139 dans docker build est un cas utilisant os/exec de Go et un tableau argv, sans passer par un shell
    • Le fragment #ref:dir d’une URL de contexte Git était transmis à git fetch origin <ref>, et <ref> était interprété comme --upload-pack=<cmd>

Des vulnérabilités répétées dans plusieurs systèmes de gestion de versions

  • En août 2017, quatre systèmes de gestion de versions ont vu le même schéma publié le même jour
  • Les quatre systèmes transmettaient le nom d’hôte de l’URL comme argument SSH, et un nom d’hôte commençant par -oProxyCommand= était traité comme une option SSH
  • Selon l’analyse post-mortem de Phabricator, parmi les trois outils alors maintenus activement, seul Subversion a ajouté -- avant le nom d’hôte
    • Git et Mercurial ont validé le format du nom d’hôte, notamment parce que toutes les implémentations SSH ne prennent pas en charge --
  • Le code dépourvu de -- semble normal et fonctionne tant que l’argument ne commence pas par un tiret ; ce mécanisme est donc intrinsèquement non sûr

Les chemins d’exposition des gestionnaires de paquets

  • Les gestionnaires de paquets reçoivent des URL Git ou des refs depuis les manifestes, les fichiers de verrouillage et les métadonnées de dépendances transitives, puis les transmettent à des sous-processus
    • gem 'foo', git: '...' dans un Gemfile
    • github:user/repo#ref dans package.json
    • Les configurations équivalentes dans pyproject.toml, Cargo.toml, mix.exs, Package.swift, pubspec.yaml, conanfile.py et go.mod
  • Sur les 19 gestionnaires de paquets examinés à la date du HEAD de juillet 2026, 17 exécutent le binaire Git comme chemin par défaut ou unique
  • Les deux autres utilisent une bibliothèque par défaut
  • Nix utilise libgit2 pour lire les dépôts locaux, mais exécute un processus Git pour les fetchs, car libgit2 ne prend pas en charge les assistants git-credential
  • Les outils étudiés sont Bundler, Cargo, CocoaPods, Composer, Conan, Go, Helm, Homebrew, Mix, Nix, npm, pip, pnpm, Poetry, Pub, SwiftPM, uv, vcpkg et Yarn

CVE confirmées dans des gestionnaires de paquets

État réel des défenses et correction de Go

  • Parmi les 17 gestionnaires de paquets qui exécutent un processus Git, le seul outil à utiliser --end-of-options était cmd/go de Go
  • En juin 2019, Go a ajouté -- avant les URL de dépôt dans le cadre d’un durcissement général
  • En janvier 2026, il est apparu que -- seul ne suffisait pas ; le correctif de CVE-2025-68119 a donc ajouté --end-of-options de manière généralisée
  • Le même correctif inclut aussi HGPLAIN=+strictflags
    • Ce réglage limite l’interprétation initiale des options par Mercurial depuis 2017, avec la sortie de Mercurial 4.4.2
  • Le commit de correction de Go indique qu’un changement plus structurel pourrait être nécessaire pour éviter que le même problème soit réintroduit, mais qu’il corrige d’abord le problème actuel

La plupart des protections ont été ajoutées après publication de vulnérabilités

  • Les autres gestionnaires de paquets, lorsqu’ils protègent la liste d’arguments, utilisent principalement -- ou le rejet des tirets en début d’entrée
  • Le -- avant l’URL dans git clone chez Bundler a été ajouté par le patch de CVE-2021-43809
  • Le rejet des tirets initiaux dans cocoapods-downloader a été appliqué en mars 2022 via trois commits sur dix jours, à la période de publication de CVE-2022-21223
  • La défense de Poetry a été ajoutée en septembre 2021, une CVE a été assignée un an plus tard, puis le projet est passé à dulwich six mois après
  • vcpkg fait exception : il utilise -- depuis le premier jour du support des registres Git

Les contraintes de compatibilité imposées par la version minimale de Git

  • L’avis CVE-2022-24828 de Composer identifie --end-of-options comme le correctif approprié, mais Composer devant prendre en charge des versions plus anciennes de Git, il a choisi de rejeter les noms de branche commençant par un tiret
  • L’intégration Git de vcpkg indique Git 2.7.4 comme version minimale, tandis que HOMEBREW_MINIMUM_GIT_VERSION pour Linux dans Homebrew est 2.7.0, fixé en 2018
  • Amazon Linux 2, qui fournissait Git 2.14.3, est arrivée en fin de vie en juin 2026 ; les distributions que suivaient ces planchers sortent donc seulement maintenant du périmètre de support
  • Le statut LTS d’Ubuntu complique également une transition généralisée
    • Ubuntu 18.04 fournit Git 2.17.0 et bénéficie d’un support étendu jusqu’en 2028
    • Ubuntu 20.04 fournit Git 2.25.1 et bénéficie d’un support étendu jusqu’en 2030
    • Git 2.25.1 accepte --end-of-options pour git fetch, mais le rejette dans git rev-parse
  • Pour s’appuyer sur --end-of-options, il faut exiger au minimum Git 2.24.0 pour la plupart des sous-commandes, 2.30.0 pour rev-parse, et 2.43.1 pour checkout et reset
  • Relever la version minimale empêche de prendre en charge les utilisateurs qui utilisent l’ancien Git fourni par leur distribution

Utiliser une bibliothèque Git plutôt qu’exécuter un processus

  • libgit2, gitoxide, go-git, JGit et dulwich implémentent dans le processus les protocoles de transport Git nécessaires au clone et au fetch
  • Comme il n’y a pas de frontière argv séparée, il n’existe tout simplement pas de cible dans laquelle injecter une liste d’arguments
  • Jujutsu utilise gitoxide pour l’intégration Git et ne fait l’objet d’aucune CVE publique de type injection d’arguments
    • Les deux avis publiés à ce jour concernent une traversée de chemin et l’absence, héritée d’une bibliothèque, d’une vérification des collisions SHA-1
  • CVE-2025-21613 dans go-git se limite au transport file://
    • Ce chemin est le seul chemin de code dans go-git qui exécute le binaire Git
  • Embarquer sa propre implémentation de Git oblige à suivre tous les correctifs de sécurité d’upstream Git liés au checkout ; libgit2 comme JGit ont déjà connu des correctifs répétés sur ce sujet
  • Ce coût est réel, mais il transforme le problème : au lieu de devoir se souvenir durablement de vérifier les arguments à chaque point d’appel, il faut appliquer un flux concret de correctifs upstream

Portée des changements proposés à Homebrew

  • La PR Homebrew relève la version minimale de Git à 2.30.0 et ajoute --end-of-options aux emplacements suivants
    • Avant les URL de clone, remote set-url et ls-remote
    • Avant les refs de rev-parse
  • Les appels à checkout et reset ne sont pas modifiés
    • Pour protéger aussi ces deux commandes, il faudrait Git 2.43.1, sorti en février 2024
    • Cette version est plus récente que le Git fourni par plusieurs distributions actuellement prises en charge

1 commentaires

 
GN⁺ 6 시간 전
Avis sur Lobste.rs
  • Ces derniers temps, je suis de plus en plus accro à jj. Comparé à ce bazar de Git, c’est presque reposant ; je suis encore en train de l’apprendre, mais il reflète bien les intentions et permet aussi de comprendre facilement le flux de travail voulu.
    En particulier, ce qu’on apprend avec une commande jj s’applique naturellement aux autres commandes. La documentation de git log voit exploser le nombre de flags selon les options de sortie, tout en mélangeant la « notation spéciale » de la syntaxe des plages de commits et des flags de filtrage supplémentaires, et les connaissances acquises se transfèrent peu aux autres commandes Git.
    À l’inverse, la documentation de jj log est si concise qu’il reste de la place sur une page. Elle remplace la complexité fourre-tout de Git par trois éléments — ensembles de révisions, ensembles de fichiers et DSL de templates de sortie — utilisés de façon cohérente dans tout jj, ce qui le rend bien plus simple, composable et intuitif. Il faut tenir compte du fait que Git a accumulé vingt ans de scories, mais jj semble bien mieux placé pour les éviter.
  • Je sais qu’il faut généralement -- avant les arguments fournis par l’utilisateur dans une commande, mais demander en plus ici des règles de transformation propres devient un piège beaucoup trop dangereux.
  • C’est le résultat honnête d’un outil rendu excessivement complexe. Git ressemble au nouveau wasaho.
  • Je me demande pourquoi -- a été choisi à l’origine pour lever l’ambiguïté des arguments de fichiers. Je me demande s’il y avait un compromis de conception moins visible derrière ce choix, plutôt qu’une approche simple consistant à recevoir les fichiers comme arguments explicites.
    • Utiliser une convention UNIX de parsing des options déjà établie à une tout autre fin était, comme on pouvait s’y attendre, stupide — un choix tout à fait dans l’esprit de Git.
  • La philosophie UNIX du tout est texte cause encore des problèmes. La ligne de commande est pourtant un exemple parfait de données structurées, mais le coût de coordination collective pour sortir de ce trou semble trop élevé.
    En repensant aux débats des années 1990 entre Tcl et Scheme, dans ce cas, « le pire est le mieux » avait peut-être raison. Alors que la famille des S-expressions, JSON et XML compris, ne s’est quasiment pas imposée dans l’écosystème UNIX, Tcl disposait d’une approche relativement principielle consistant à superposer des sous-langages à l’intérieur de chaînes.