Aller au contenu

CWE-78 : injection de commande système, quand le programme appelle un interpréteur

CWE-78 : injection de commande système, quand le programme appelle un interpréteur

Section intitulée « CWE-78 : injection de commande système, quand le programme appelle un interpréteur »

Neuvième du CWE Top 25 2025. Le libellé officiel est Improper Neutralization of Special Elements used in an OS Command (‘OS Command Injection’) : « neutralisation incorrecte d’éléments spéciaux utilisés dans une commande du système d’exploitation ». Le catalogue retient aussi shell injection et, plus précisément, shell metacharacters : car c’est bien de métacaractères qu’il s’agit, ces signes qui, dans un interpréteur de commandes, ne sont pas du texte mais de la grammaire.

→ Lire l’explication complète de la marmotte

Le produit construit tout ou partie d’une commande du système à partir d’une entrée influencée depuis l’extérieur, sans neutraliser les éléments spéciaux qui peuvent modifier la commande prévue.

La définition suppose donc une chose essentielle : il y a un interpréteur quelque part. Et c’est tout le sujet, car la faiblesse n’apparaît que lorsqu’un programme, au lieu d’utiliser les fonctions de son langage pour copier un fichier ou interroger le réseau, demande au système d’exécuter une ligne de commande. Un programme qui n’invoque jamais d’interpréteur ne peut pas porter cette faiblesse.

Il faut distinguer deux situations que le catalogue traite ensemble mais qui ne se corrigent pas de la même façon :

  • l’application laisse choisir la commande : l’entrée extérieure décide du programme invoqué. Rare, et généralement le signe d’une conception à revoir ;
  • l’application fixe la commande et laisse choisir un argument : c’est le cas courant, et le défaut tient aux métacaractères (point-virgule, barre verticale, apostrophes inversées, substitutions), qui permettent d’ajouter une commande à la suite de l’argument.

Une troisième situation, voisine et distincte, est signalée par la relation CanAlsoBe vers CWE-88 : l’injection de délimiteur d’argument, où l’entrée ne lance pas de seconde commande mais ajoute une option à la première. Un nom de fichier qui commence par un tiret n’exécute rien de nouveau ; il change le comportement du programme appelé, ce qui suffit parfois largement.

Sa place dans l’arbre : CWE-77, CWE-78 et CWE-94

Section intitulée « Sa place dans l’arbre : CWE-77, CWE-78 et CWE-94 »

C’est ici que le catalogue devient réellement utile, car trois entrées voisines se confondent dans tous les rapports.

CWE-78 est une base, fille de la classe CWE-77, l’injection de commande au sens large, qui figure elle aussi au Top 25, au rang 23. La différence est de périmètre : CWE-77 couvre toute commande destinée à un interpréteur, quel qu’il soit (un moteur de recherche interne, un langage de requête maison, une file de tâches) ; CWE-78 restreint au cas où l’interpréteur est celui du système d’exploitation. Un rapport qui code en CWE-77 une injection vers un interpréteur de commandes système n’est pas faux, il est trop haut d’un cran ; l’inverse, coder en CWE-78 une injection vers un interpréteur applicatif, est une erreur de fait.

Troisième voisine, CWE-94, l’injection de code, au rang 10. La distinction tient à ce qui est injecté : du code dans le langage du programme pour CWE-94 (une expression évaluée à l’exécution, une fonction construite dynamiquement), une commande pour un interpréteur externe pour CWE-78. Dans le premier cas, l’attaquant écrit dans le langage de l’application ; dans le second, il écrit dans celui du système.

La classe partage enfin son grand-parent avec toutes les injections : CWE-74, la neutralisation incorrecte d’éléments spéciaux dans une sortie destinée à un composant en aval. Même mécanisme, autre composant en aval.

Parce que l’appel système est la solution de facilité. Redimensionner une image, convertir un document, tester une adresse réseau, archiver un dossier : dans chaque cas il existe un utilitaire en ligne de commande qui fait le travail en une ligne, alors que la bibliothèque équivalente demande vingt lignes et une dépendance. Le choix se fait tous les jours, souvent dans un script d’administration que personne ne considère comme du code de production.

Parce que les équipements concernés sont les plus exposés. Interfaces web de routeurs, caméras, enregistreurs vidéo, systèmes de stockage en réseau, passerelles : ces appareils reposent massivement sur des scripts d’interface qui transmettent les saisies à l’interpréteur du système. La conséquence directe est une exécution de commandes avec les privilèges du service, souvent les plus élevés, sur un appareil joignable depuis Internet.

Parce que la gravité est maximale par nature. Cette faiblesse ne divulgue pas, ne corrompt pas : elle exécute. Dans un classement qui multiplie fréquence et gravité, un défaut systématiquement critique monte haut même avec une fréquence moyenne.

Le site aborde la question par le métier concerné : écrire des scripts Bash système fiables et sûrs traite des métacaractères, de la citation des variables et de la séparation des mots, qui sont exactement la matière de cette faiblesse ; Python pour l’automatisation défensive montre côté langage la différence entre lancer un programme avec une liste d’arguments et lui passer une ligne de commande à interpréter, la frontière qui sépare le code sain du code vulnérable.

Deux renvois pour l’environnement d’exécution : la pollution de variable d’environnement héritée montre qu’une commande peut être détournée sans qu’on touche à ses arguments, et l’élévation de privilège par binaire SUID explique ce que devient une injection quand le processus appelant tourne avec des droits qu’il n’utilise pas.

Ne pas invoquer d’interpréteur. C’est la seule correction définitive : utiliser la bibliothèque du langage plutôt que l’utilitaire externe. Quand c’est impossible, appeler le programme directement, avec ses arguments sous forme de liste, sans passer par un interpréteur de commandes : la plupart des langages proposent les deux formes, et la forme sûre est rarement la forme par défaut des exemples trouvés en ligne.

Valider par liste blanche quand l’entrée décide. Si une saisie choisit un format de conversion ou un nom d’archive, la liste des valeurs admises se tient en quelques lignes. Un échappement de métacaractères écrit à la main ne tient jamais : la liste des signes spéciaux dépend de l’interpréteur, de sa version et de la locale.

Réduire les droits du processus. Un service qui appelle un utilitaire n’a presque jamais besoin des privilèges d’administration qu’il détient. Cette réduction ne corrige rien, mais elle change ce que l’injection permet, et c’est souvent la différence entre un incident et une compromission complète.

Une équipe qui ne distingue pas « lancer un programme » de « faire interpréter une ligne de commande » écrit la seconde forme partout, parce que c’est celle des exemples. Le code fonctionne, les tests passent, et la faiblesse reste invisible jusqu’à ce qu’une saisie contienne un point-virgule. Le dégât est alors d’une nature différente des autres injections : il n’y a ni lecture de base de données ni fuite de fichiers, il y a exécution de commandes sur le serveur, avec les droits du service, c’est-à-dire la possibilité d’installer un accès permanent. Le coût de l’enquête est également supérieur : une fois l’exécution obtenue, l’attaquant n’a plus besoin de la faille, et les traces à chercher ne sont plus dans les journaux de l’application mais dans ceux du système, quand ils existent, ce qui est rarement le cas sur les équipements où cette faiblesse prospère.

Un système d’exploitation possède un interpréteur de commandes : un programme auquel on écrit des lignes de texte qu’il exécute. Dans ces lignes, certains signes ne sont pas du texte mais de la ponctuation grammaticale. Le point-virgule, par exemple, signifie « et ensuite, fais ceci ».

Beaucoup de programmes, pour accomplir une tâche, préfèrent demander à cet interpréteur de lancer un utilitaire existant plutôt que d’écrire le travail eux-mêmes : c’est plus court et cela fonctionne. Ils fabriquent donc une ligne de commande, en y insérant ce que l’utilisateur a saisi : le nom d’un fichier, une adresse à tester.

Si l’utilisateur écrit, au lieu d’un nom de fichier, un nom suivi d’un point-virgule et d’une autre commande, l’interpréteur exécute les deux. Il n’a aucun moyen de savoir laquelle des deux le programmeur voulait : il lit une ligne, il l’exécute entièrement.

C’est le défaut que le catalogue CWE appelle CWE-78. Il est particulièrement grave parce qu’il ne donne pas accès à des données, mais à l’exécution de programmes sur la machine. Il est aussi très fréquent sur les appareils dont l’interface web est faite de petits scripts : routeurs, caméras de surveillance, boîtiers de stockage, dont les correctifs arrivent tard et s’installent rarement.

La bonne pratique est connue et simple à énoncer : ne pas fabriquer de ligne de commande. Les langages de programmation permettent de lancer un programme en lui donnant ses arguments séparément, sans passer par l’interpréteur ; les signes de ponctuation redeviennent alors du texte ordinaire, et il n’y a plus de grammaire à détourner.

À lire aussi

Pages voisines

: les deux pages se citent l’une l’autre.