Redécouvrir l’injection d’arguments avec les outils VCS — git et mercurial
Alessio Della Libera
23 août 2022
0 minutes de lectureL’un des principaux objectifs de cette recherche était d’examiner comment exécuter des commandes arbitraires même avec une API sécurisée qui empêche l’injection de commandes. Nous nous intéressons aux outils de gestion de versions (VCS), comme git et hg (mercurial), qui permettent, parmi d’autres options, d’exécuter des commandes arbitraires dans certaines circonstances.
Cette recherche porte sur les applications web et les bibliothèques (écrites dans n’importe quel langage de programmation) qui appellent ces commandes à l’aide d’une API sécurisée. Nous n’avons pas ciblé les projets CLI ni les projets qui appellent ces commandes en utilisant des données issues de sources stockées, comme des fichiers de configuration.
Dans cet article, nous présentons des scénarios courants où, même avec une API sécurisée, l’injection d’options d’argument peut permettre d’exécuter des commandes arbitraires. Nous proposons également des exemples de mesures correctives et des recommandations à l’intention des responsables de projets open source.
Contexte de l’injection d’arguments
Considérons le scénario suivant : un développeur souhaite appeler les commandes git ou hg depuis le répertoire de son projet afin d’interagir avec des dépôts. Toutefois, des valeurs contrôlées par l’utilisateur sont utilisées comme paramètres de ces commandes (l’utilisateur peut ainsi spécifier l’URL du dépôt, le nom de la branche, etc.). Conscient du risque d’injection de commandes, le développeur décide d’utiliser une API sécurisée pour exécuter les commandes shell. Par exemple, si le projet est en Node.js, l’API spawn du module child_process empêche l’injection de commandes lorsqu’elle est appelée de la manière suivante :
Mais ce n’est pas toujours le cas. Comme nous allons le voir, cela dépend aussi de la commande appelée par ces API. Pour en savoir plus sur l’injection d’arguments, cet article de Staaldraad est une excellente ressource.
Pourquoi faut-il se préoccuper de git ?
Certaines sous-commandes git proposent des options permettant à l’utilisateur de spécifier les programmes ou commandes à exécuter. C’est notamment le cas de l’option --upload-pack, disponible pour les sous-commandes git suivantes :
Prenons par exemple les sous-commandes git fetch, ls-remote et pull, qui prennent en charge l’option --upload-pack.
Selon la commande, certains arguments sont obligatoires et peuvent être suivis d’arguments facultatifs. Par exemple, git fetch nécessite au moins un argument, c’est-à-dire un dépôt, suivi de refspec et d’un ensemble d’options.
L’exécution des commandes suivantes dans un terminal lancera la commande shell touch HELLO :
À quoi ressemble du code vulnérable avec « git » ?
Pour illustrer cette vulnérabilité, j’utiliserai JavaScript et Python. Pour simplifier les exemples, supposons que les valeurs url et branch sont contrôlées par l’utilisateur et ne sont pas correctement assainies.
L’exemple suivant montre qu’il est toujours possible d’exécuter du code arbitraire, même avec une API sécurisée qui protège contre les injections de commandes classiques :
JavaScript (NodeJS)
La dernière ligne montre qu’une charge utile d’injection de commandes classique (c’est-à-dire une charge utile contenant des caractères spéciaux comme ; & | , etc. pour enchaîner des commandes) ne fonctionne pas.
Python
Il est important de noter que les scénarios ci-dessus peuvent entraîner l’exécution de commandes si les arguments requis par les sous-commandes git correspondantes sont fournis. Par exemple, si un seul argument est fourni à la sous-commande git ls-remote sans aucun autre argument, les scénarios précédents ne fonctionneront pas :
JavaScript (NodeJS)
En effet, l’unique argument fourni sera interprété comme un dépôt (dans le cas de git ls-remote). Si la commande reçoit un autre argument valide, on retrouve le scénario précédent et il est toujours possible d’exécuter du code.
Mesures correctives
Une solution possible aux scénarios précédents consiste à ajouter -- avant les valeurs contrôlées par l’utilisateur. Comme l’explique la documentation officielle, « lorsque vous écrivez un script destiné à traiter des données saisies par des utilisateurs, il est recommandé de préciser les arguments en plaçant des séparateurs -- aux endroits appropriés. »
Par exemple, le code suivant ne permettra plus d’exécuter des commandes :
Pourquoi faut-il se préoccuper de hg (mercurial) ?
Comme nous l’avons vu dans la section précédente, certaines conditions doivent être remplies pour exécuter des commandes arbitraires (arguments obligatoires). Toutefois, ce n’est pas le cas avec hg — l’exploitation est donc beaucoup plus facile.
Parmi les nombreuses options disponibles, mercurial permet aux utilisateurs de définir des aliases et des hooks pour chaque commande (clone, init, log, etc.) à l’aide de l’option –config.
Comme l’explique la documentation de mercurial, les alias « vous permettent de définir vos propres commandes à partir d’autres commandes (ou alias), avec la possibilité d’y inclure des arguments. Un alias peut commencer par un point d’exclamation (« ! ») pour devenir un alias shell. Il est alors exécuté par le shell, ce qui permet de lancer des commandes arbitraires, par exemple :
vous permettra de saisir "hg echo foo" pour afficher "foo" dans votre terminal. »
Les hooks, quant à eux, sont des « commandes ou fonctions Python exécutées automatiquement par diverses actions, comme le début ou la fin d’un commit ». Ces commandes sont exécutées lorsque certaines conditions sont remplies, avant (pre-<command>) ou après (post-<command>) d’autres commandes mercurial.
À quoi ressemble du code vulnérable lors de l’utilisation de « hg init » ?
Voyons un exemple montrant comment exécuter des commandes arbitraires lorsque la commande hg init est appelée avec des entrées utilisateur non assainies.
Exemple d’alias
La commande touch HELLO sera exécutée. En effet, un alias de la commande init est défini et exécuté à la place de la commande hg init.
La même commande exécutée depuis du code se présente comme suit (comme précédemment, supposons que la valeur source est contrôlée par l’utilisateur et n’est pas correctement assainie) :
JavaScript (NodeJS)
Python
Exemple de hooks
La commande touch HELLO sera exécutée avant la commande init (c’est-à-dire grâce aux options pre-<command>).
La même commande exécutée depuis du code se présente comme suit :
JavaScript (NodeJS)
Python
Contrairement au cas de git, il suffit de contrôler un seul argument fourni aux commandes mercurial pour exécuter des commandes arbitraires, même s’il s’agit du seul paramètre utilisé.
Mesures correctives
Une fois encore, nous pouvons résoudre ce problème en ajoutant les caractères -- avant les arguments contrôlés par l’utilisateur.
Par exemple, l’attaque suivante ne fonctionnera plus : elle créera un dossier de dépôt dont le nom correspond au contenu de la variable source :
Résultats
Des problèmes similaires ont déjà été exploités par le passé. Parmi les exemples les plus connus, on trouve les attaques par exécution de code à distance contre CocoaPods Trunk et Phabricator.
Le tableau ci-dessous récapitule les problèmes que nous avons découverts et signalés dans le cadre de cette recherche :
Projet | Vulnérabilité | Avis/Réf. | CVE |
|---|---|---|---|
ungit | RCE | CVE-2022-25766 | |
mozilla/pontoon | RCE | N/A | |
GoCD Server | RCE | CVE-2022-29184 | |
Weblate | RCE (x2) | CVE-2022-23915 | |
simple-git | Injection de commandes | CVE-2022-24433 | |
ruby-git | Injection de commandes | CVE-2022-25648 | |
cocoapods-downloader | Injection de commandes | CVE-2022-24440 | |
cocoapods-downloader | Injection de commandes | CVE-2022-21223 | |
Masterminds/vcs | Injection de commandes | CVE-2022-21235 | |
git-php | Injection de commandes | CVE-2022-25866 | |
libvcs | Injection de commandes | CVE-2022-21187 | |
workspace-tools | Injection de commandes | CVE-2022-25865 | |
cookiecutter | Injection de commandes | CVE-2022-24065 | |
hashicorp/go-getter | Injection de commandes | CVE-2022-26945 |
Après avoir découvert ces vulnérabilités, nous les avons signalées en privé aux responsables, conformément à notre politique de divulgation des vulnérabilités. Je tiens à remercier personnellement toutes les personnes que j’ai contactées pour leur temps et leur collaboration.
Dans les études de cas suivantes, nous expliquerons comment il a été possible de provoquer une exécution de code à distance et une injection de commandes dans ces projets populaires.
Étude de cas : exécution de code à distance dans Weblate
Weblate est un « outil de localisation web doté d’une intégration étroite avec le contrôle de versions ». Un utilisateur authentifié pouvait obtenir une exécution de code à distance par injection d’arguments.
Lors de l’ajout de composants de traduction et de l’utilisation de dépôts mercurial, l’entrée utilisateur branch est utilisée dans la commande hg pull sans assainissement approprié.
Le code concerné, qui appelle la commande hg pull avec des valeurs contrôlées par l’utilisateur, est le suivant :
Preuve de concept
Pour exécuter des commandes arbitraires, l’utilisateur doit créer un nouveau composant de traduction dans un projet de traduction, avec la valeur Branche du dépôt définie sur --config=alias.pull=!id>/app/cache/static/output_rce1.txt. Dans ce cas, la commande id sera exécutée et sa sortie redirigée vers une page accessible à l’adresse http://localhost:8888/static/output_rce1.txt.
1. Connectez-vous à l’application.
2. Ajoutez un projet de traduction, puis cliquez sur Enregistrer.

3. Ajoutez un composant de traduction au projet créé, en saisissant la charge utile --config=alias.pull=!id>/app/cache/static/output_rce1.txt dans le champ Branche du dépôt, puis cliquez sur Continuer.

4. La sortie de la commande id sera accessible à l’adresse http://localhost:8888/static/output_rce1.txt.

Mesure corrective
Comme vous pouvez le constater, il a été possible d’obtenir une RCE même avec des dépôts git. Dans ce cas, l’entrée utilisateur non assainie repo était transmise à la commande git ls-remote.
Le responsable a rapidement corrigé les deux problèmes dans la version 4.11.1 (voir la publication Github). Les correctifs (cas mercurial et cas git) ajoutaient les caractères -- avant l’entrée utilisateur afin d’éviter qu’elle soit interprétée comme un argument.
Étude de cas : injection de commandes dans ruby-git
Ruby git est une « bibliothèque qui permet de créer, lire et manipuler des dépôts Git en encapsulant les appels système au binaire git ».
Lors de l’appel de la fonction fetch(remote = 'origin', opts = {}), le paramètre remote est transmis à la sous-commande git fetch sans assainissement. Comme nous l’avons vu précédemment, git fetch fait partie des sous-commandes git qui acceptent l’argument --upload-pack. Pour en savoir plus sur --upload-pack, consultez la documentation de git-fetch.
Le code concerné, qui appelle la commande git fetch avec des valeurs contrôlées par l’utilisateur, est le suivant :
Preuve de concept
La PoC suivante montre comment il a été possible d’exécuter des commandes arbitraires :
Mesure corrective
Le problème a été corrigé par les responsables de la maintenance dans la version 1.11.0 (version GitHub). Comme pour le problème précédent, le correctif a ajouté les caractères -- avant les valeurs contrôlées par l’utilisateur.
À retenir
… pour les développeurs
Lorsque vous appelez des commandes à l’aide d’une API sécurisée — qui empêche l’injection de commandes et accepte des valeurs contrôlées par l’utilisateur — il est important de veiller à ce que les valeurs fournies par l’utilisateur ne modifient pas le comportement de la commande en injectant ou en manipulant certaines options. Pour éviter toute confusion, les caractères -- peuvent servir à séparer les arguments des valeurs contrôlées par l’utilisateur.
… pour les responsables de la maintenance
Si une fonction ou une API encapsule des appels à des commandes qui acceptent des valeurs contrôlées par l’utilisateur, il peut être utile d’en documenter le comportement. Cela évitera de dérouter les utilisateurs qui constatent que la personne responsable de la maintenance traite certains problèmes de désinfection (par exemple, en empêchant l’injection de commandes à l’aide d’une API sécurisée), mais pas d’autres (par exemple, lorsque l’entrée peut être utilisée comme option dangereuse et entraîner un comportement inattendu).
… pour les chercheurs en sécurité
Nous nous sommes concentrés sur les commandes git et hg, mais bien d’autres fonctionnent de manière similaire. Il convient de noter que même l’utilisation d’API sécurisées qui empêchent l’injection de commandes peut entraîner des problèmes de sécurité. Tout dépend des commandes exécutées. Si vous découvrez des problèmes similaires (ou toute autre vulnérabilité de sécurité) dans un projet open source soutenu par notre programme, veuillez le signaler à l’aide du formulaire de signalement des vulnérabilités de Snyk.



