Comprendre les vulnérabilités d’injection de commandes en Go
14 novembre 2024
0 minutes de lectureLes développeurs Go peuvent avoir besoin d’utiliser des commandes système dans différents cas, par exemple pour manipuler des images, les traiter ou les redimensionner, gérer des ressources, ou collecter des métriques ou des journaux.
Il peut aussi arriver que vous développiez un nouveau système en Go qui doit s’interfacer avec des systèmes existants plus anciens. Cette interface repose sur l’exécution de commandes système et le traitement de leur sortie.
Dans les deux cas, il est essentiel de respecter les conventions de codage sécurisé lors du lancement de commandes système, car cela pourrait entraîner une vulnérabilité de sécurité appelée injection de commandes.
Qu’est-ce que l’injection de commandes ?
L’injection de commandes est une vulnérabilité de sécurité qui survient lorsqu’une application transmet au shell système des données non fiables fournies par l’utilisateur, comme celles saisies dans un formulaire Web. Cette faille permet à un attaquant d’exécuter des commandes arbitraires sur le système d’exploitation hôte, avec les mêmes droits que l’utilisateur de l’application.
En Go, une injection de commandes implique souvent l’utilisation du package os/exec pour lancer des commandes système.
Prenons l’exemple de code Go suivant :
Dans cet extrait de code, cmdName est extrait directement des paramètres de requête et utilisé pour construire une commande exécutée sur le serveur. Il s’agit d’un exemple classique de vulnérabilité par injection de commandes : un attaquant peut manipuler la valeur de la chaîne de requête cmd pour exécuter n’importe quelle commande, et potentiellement compromettre le serveur.
Un attaquant pourrait créer une requête contenant une commande malveillante, par exemple :
Pourquoi l’injection de commandes est-elle dangereuse ? Elle permet aux attaquants d’exécuter des commandes arbitraires sur le serveur, ce qui peut avoir de graves conséquences, notamment :
Violation de données : les attaquants peuvent accéder aux données sensibles stockées sur le serveur. Imaginez qu’ils accèdent à des fichiers de configuration tels que
config.tomlet à d’autres fichiers répertoriant les ressources et les identifiants.Compromission du système : les attaquants peuvent prendre le contrôle du serveur et poursuivre leur exploitation. Cela prend souvent la forme de déplacements latéraux au sein du système compromis et d’opérations de reconnaissance visant d’autres serveurs susceptibles d’être compromis.
Interruption de service : les attaquants peuvent exécuter des commandes qui perturbent les services et provoquent des interruptions. Cela peut directement se traduire par des pertes financières et des risques pour l’activité dus à une attaque par déni de service.
Les conséquences d’une attaque par injection de commandes réussie peuvent être dévastatrices : elles touchent non seulement le système compromis, mais aussi la réputation de l’organisation et la confiance de ses clients.
Exécution de processus vulnérable et injection de commandes en Go
Les développeurs Go, connus pour privilégier la simplicité et les performances, peuvent choisir d’intégrer des commandes système afin de tirer parti des capacités de ces utilitaires. Cette approche leur permet de se concentrer sur la création d’applications robustes sans réinventer la roue. Elle comporte toutefois ses propres difficultés, notamment en matière de sécurité.
Pour illustrer un cas plus réaliste, prenons une application Go qui traite des fichiers image à l’aide d’un utilitaire en ligne de commande comme convert.
Examinons une application Go conçue pour traiter des demandes de redimensionnement d’images. Elle utilise le framework Web Gin pour définir un point de terminaison POST, /cloudpawnery/image, qui redimensionne les images selon les données saisies par l’utilisateur. Ce point de terminaison accepte des paramètres tels que tenantID, fileID et fileSize dans la chaîne de requête. Le paramètre fileSize est facultatif et prend la valeur « 200 » par défaut s’il n’est pas renseigné.
L’extrait de code suivant illustre une implémentation vulnérable en Go.
La fonction downloadAndResize construit une chaîne de commande pour redimensionner l’image avec convert, puis l’exécute à l’aide de exec.CommandContext.
Comment la fonction downloadAndResize construit-elle une chaîne de commande ? Elle utilise la valeur fileSize fournie par l’utilisateur. Cette chaîne est ensuite exécutée, ce qui peut permettre à un attaquant d’injecter des commandes malveillantes. Pour limiter ce risque, les développeurs Go doivent valider et assainir toutes les entrées utilisateur, utiliser des commandes paramétrées et appliquer des pratiques de sécurité qui permettent de gérer l’exécution des commandes sans danger.
Prévenir les vulnérabilités par injection de commandes
En développement Go, comme dans tout autre langage, il est primordial de protéger votre code contre les vulnérabilités par injection de commandes. Celles-ci surviennent lorsqu’un attaquant peut exécuter des commandes arbitraires sur l’hôte, ce qui peut entraîner un accès non autorisé, des violations de données et d’autres graves problèmes de sécurité.
Découvrons quelques bonnes pratiques pour réduire ces risques.
Validation et assainissement des entrées
L’une des premières mesures pour prévenir l’injection de commandes consiste à valider et à assainir rigoureusement les entrées. Dans l’exemple présenté, la fonction downloadAndResize construit une chaîne de commande à partir de valeurs fournies par l’utilisateur, comme fileSize. Si ces entrées ne sont pas correctement validées, un attaquant peut injecter des commandes malveillantes.
Voici comment améliorer la validation des entrées :
Autoriser uniquement les valeurs répertoriées : définissez un ensemble de valeurs acceptables pour les entrées comme
fileSize. Par exemple, n’autorisez que des valeurs numériques comprises dans une plage raisonnable.Assainir les entrées : supprimez ou échappez tout caractère potentiellement dangereux fourni par l’utilisateur. Il peut s’agir de métacaractères du shell tels que
;,|,&, etc. Il est préférable de coder la commande en dur et de ne pas laisser l’utilisateur la contrôler.Utiliser des types stricts : convertissez les entrées dans le type de données attendu dès que possible. Par exemple, convertissez
fileSizeen entier et vérifiez que sa valeur se trouve dans la plage autorisée.
Voici un exemple d’implémentation de ces pratiques :
Même ainsi, il est possible de mieux protéger le code Go contre l’injection de commandes. Poursuivez votre lecture !
Utiliser des API ou des bibliothèques sûres plutôt que des commandes système
Une autre stratégie efficace consiste à éviter autant que possible l’exécution directe de commandes système. Préférez des API ou des bibliothèques sûres qui offrent les fonctionnalités nécessaires sans exposer votre application aux risques d’injection de commandes.
Par exemple, si votre application doit manipuler des images, envisagez d’utiliser une bibliothèque Go comme github.com/disintegration/imaging plutôt que d’appeler une commande externe comme convert de la bibliothèque logicielle ImageMagick. Cette approche encapsule les fonctionnalités dans l’environnement Go à sécurité des types, ce qui réduit la surface d’attaque.
En utilisant des bibliothèques comme imaging en Go, vous n’avez plus besoin de construire et d’exécuter des commandes shell, ce qui réduit le risque d’injection de commandes. Toutefois, dans certaines bibliothèques et certains écosystèmes de langages, un package tiers peut se révéler être un simple wrapper autour de l’exécution de commandes. Il est donc indispensable d’examiner le code pour repérer ce type d’opérations sensibles.
Refactoriser la fonction vulnérable downloadAndResize pour prévenir l’injection
Dans l’exemple précédent, nous avons présenté une application Go potentiellement vulnérable qui utilise la fonction exec.CommandContext pour exécuter des commandes shell. Comme nous l’avons indiqué, cette approche peut entraîner des vulnérabilités par injection de commandes.
Essayons de refactoriser la fonction downloadAndResize afin que les entrées utilisateur ne permettent pas d’exécuter des commandes arbitraires.
Pour prévenir l’injection de commandes, une méthode efficace consiste à ne pas construire directement des chaînes de commandes shell à partir des entrées utilisateur. Utilisez plutôt la fonction exec.Command avec des arguments séparés. Vous pouvez ainsi transmettre les entrées utilisateur à la commande en toute sécurité, sans invoquer le shell ni permettre aux utilisateurs de contrôler la commande elle-même.
Voici une version refactorisée de la fonction downloadAndResize qui corrige la vulnérabilité par injection de commandes :
Dans cette refactorisation, nous avons séparé la commande de ses arguments. En utilisant exec.CommandContext avec des arguments distincts, nous évitons d’avoir à construire une chaîne de commande shell. Ainsi, les entrées utilisateur sont traitées comme des données et non comme du code exécutable, ce qui réduit considérablement le risque d’injection de commandes.
Nous avons également éliminé le besoin d’invoquer le shell. Le code refactorisé n’appelle pas le shell (sh -c), qui constitue un vecteur courant d’injection de commandes. Il appelle directement l’utilitaire convert avec les arguments spécifiés.
Renforcer la sécurité du code avec Snyk
Snyk Code est un puissant outil d’analyse statique qui aide les développeurs à détecter et à corriger les vulnérabilités dans leur base de code. Il s’intègre parfaitement à votre IDE et à votre workflow de développement, et vous signale en temps réel les problèmes de sécurité potentiels.
Comment Snyk aide à détecter les vulnérabilités par injection de commandes en Go
Dans l’exemple Go présenté, la fonction downloadAndResize construit une commande shell à partir d’une entrée fournie par l’utilisateur :
Ce code est vulnérable à l’injection de commandes, car il intègre directement les entrées utilisateur dans la chaîne de commande.
Et si certains développeurs de votre équipe ne connaissaient pas les vulnérabilités par injection de commandes ?
Pourriez-vous facilement repérer le flux d’appels d’analyse statique entre fichiers lors d’une revue de code afin de détecter cette vulnérabilité par injection de commandes ?
C’est là que Snyk intervient.
Découvrez comment la sécurité des applications devient plus simple grâce à l’extension Snyk Code, qui met en évidence en temps réel le code vulnérable dans l’éditeur VS Code :

Snyk peut vous aider à détecter ce type de vulnérabilité en analysant votre code Go et en signalant les cas où des entrées utilisateur sont utilisées de manière non sécurisée dans des commandes shell. Snyk vous présente de vrais commits d’autres projets open source qui ont corrigé cette vulnérabilité précise. Vous disposez ainsi de plusieurs exemples de code illustrant les bonnes pratiques.
Mieux encore, en cliquant sur l’onglet ISSUE OVERVIEW, vous accédez à une analyse détaillée ainsi qu’aux bonnes pratiques pour prévenir l’injection de commandes. Cet onglet fournit des informations détaillées et des recommandations pour réduire ces risques. Il est accessible directement dans la vue de l’IDE pendant que vous codez, ce qui vous permet de suivre ces recommandations sans changement de contexte coûteux :

Pour en savoir plus sur la protection de votre code contre ces vulnérabilités, installez l’extension IDE Snyk Code et connectez vos projets Git afin de détecter et de corriger les problèmes de sécurité dans vos applications Go. C’est simple et gratuit : inscrivez-vous pour commencer à analyser votre code à la recherche de vulnérabilités.
Outil gratuit de vérification du code
Sécurisez votre code avant votre prochain commit.
