Aller au contenu

CWE-94 : injection de code, quand la donnée devient du programme

CWE-94 : injection de code, quand la donnée devient du programme

Section intitulée « CWE-94 : injection de code, quand la donnée devient du programme »

Dixième du CWE Top 25 2025. Le libellé officiel est Improper Control of Generation of Code (‘Code Injection’) : « contrôle incorrect de la génération de code ». Le nom officiel insiste sur la génération, et il a raison : la faiblesse n’est pas de recevoir du code, c’est d’en fabriquer avec des morceaux venus de l’extérieur.

→ Lire l’explication complète de la marmotte

Le produit construit tout ou partie d’un segment de code à partir d’une entrée influencée depuis l’extérieur, sans neutraliser les éléments spéciaux qui peuvent modifier la syntaxe ou le comportement du code prévu.

Le point à retenir est la frontière avec la classe voisine. Dans l’injection de commande système, l’attaquant écrit pour un interpréteur extérieur au programme : celui du système d’exploitation. Ici, il écrit dans le langage du programme lui-même, qui évalue son propre code à l’exécution. La conséquence est la même (exécuter ce qu’on veut), mais le chemin diffère, et la correction aussi : on ne durcit pas un appel de commande, on supprime une évaluation dynamique.

La classe couvre aussi bien le code évalué immédiatement que le code écrit pour plus tard : une entrée insérée dans un fichier de configuration qui sera chargé au prochain démarrage, ou dans une bibliothèque générée, relève de la même faiblesse, avec un délai. Ce décalage temporel rend ces cas difficiles à relier à leur cause, car l’exécution se produit longtemps après l’injection, parfois dans un autre processus.

CWE-94 est une base avec deux parents, et c’est révélateur. Dans la famille des injections, son parent est CWE-74, le tronc commun : une donnée franchit une frontière de langage sans être requalifiée. Dans la famille du code dynamique, son parent est la classe CWE-913, « contrôle incorrect des ressources de code gérées dynamiquement », qui couvre tout ce qui consiste à lire ou écrire des variables, des objets, des classes ou des fonctions désignés à l’exécution.

Trois entrées descendent d’elle, et chacune correspond à une situation que l’on rencontre vraiment :

  • CWE-95, l’injection dans une évaluation dynamique, c’est-à-dire la fonction eval et ses équivalents dans tous les langages interprétés, le cas d’école ;
  • CWE-96, l’injection dans du code enregistré : l’entrée est écrite dans un fichier qui sera exécuté ensuite ;
  • CWE-1336, l’injection dans un moteur de modèles, la plus actuelle. Un moteur de modèles est un petit langage : quand une donnée extérieure est placée non pas dans les variables fournies au modèle mais dans le texte du modèle lui-même, l’attaquant obtient l’exécution de ce petit langage, qui donne souvent accès à l’ensemble du langage hôte.

Une cousine mérite d’être citée, car elle appartient à la même famille et occupe le site : CWE-502, la désérialisation de données non fiables, au rang 15. Elle n’injecte pas de texte de code, elle injecte un état qui déclenche l’exécution de code déjà présent. Même résultat, mécanisme franchement différent.

Parce que l’évaluation dynamique est un raccourci partout disponible. Toutes les plateformes qui permettent à leurs utilisateurs d’écrire des formules, des règles, des filtres ou des extensions finissent par évaluer du texte fourni. Les outils d’automatisation, les systèmes de modèles de documents, les plateformes à faible code et les règles métier configurables sont les familles de produits les plus représentées.

Parce que les moteurs de modèles sont devenus universels. Générateurs de pages, systèmes de notification par courriel, outils de rapport : dès qu’un utilisateur peut personnaliser un modèle, la frontière entre « donnée affichée » et « modèle exécuté » devient une décision d’architecture que personne n’a prise explicitement.

Parce que la gravité est maximale. Comme pour l’injection de commande, le résultat est l’exécution de code ; le score du classement en tient compte.

Le site traite les deux voisinages immédiats. Pour la chaîne qui mène d’une donnée à l’exécution sans passer par du texte de code, la désérialisation non sécurisée et ses chaînes de gadgets est la référence ; pour la variante dans un format de configuration apparemment inoffensif, l’attaque par désérialisation YAML montre qu’un fichier de paramètres peut instancier des objets.

Deux renvois plus actuels. La pollution de prototype en JavaScript illustre le parent CWE-913 : on ne fournit pas de code, on modifie les ressources de code gérées dynamiquement, et le comportement du programme change. Et côté intelligence artificielle, valider le function calling d’un LLM traite le cas où le « code » généré est un appel de fonction produit par un modèle : la sortie d’un générateur statistique ne doit pas plus être évaluée sans contrôle qu’une saisie d’utilisateur.

Supprimer l’évaluation dynamique. Dans la quasi-totalité des cas, elle sert à contourner une absence de conception : une table de correspondance, un ensemble de fonctions nommées, un interpréteur restreint écrit pour l’occasion remplacent un eval et se testent. La règle pratique : toute évaluation dynamique encore présente dans un dépôt doit être justifiée par écrit, nominativement.

Séparer strictement le modèle des données quand un moteur de modèles est en jeu. Les données passent par les variables, jamais par le texte du modèle. Si les utilisateurs doivent pouvoir écrire des modèles, le moteur doit tourner en mode restreint, sans accès aux objets du langage hôte : et ce mode doit être vérifié, car beaucoup de moteurs annoncent une restriction qui tombe au premier attribut spécial.

Isoler l’exécution lorsqu’un langage utilisateur est une fonctionnalité assumée : processus séparé, droits réduits, limites de temps et de mémoire. On ne protège plus le code, on contient ce qu’il peut faire.

Une équipe qui ne nomme pas cette classe laisse subsister les évaluations dynamiques héritées, chacune justifiée par une petite commodité : une formule paramétrable dans un écran d’administration, un filtre personnalisable dans un export, un modèle de courriel que le service commercial peut modifier. Aucune de ces fonctions ne ressemble à du code, et c’est précisément le problème : personne ne les relit comme telles. Le jour où l’une d’elles est atteinte par une saisie non filtrée, l’attaquant n’obtient pas des données mais l’exécution, dans le processus de l’application, avec ses accès à la base et à ses secrets. Le coût de l’enquête est aggravé par le décalage temporel propre à cette classe : quand l’injection a été écrite dans un fichier de configuration ou un modèle enregistré, l’exécution se produit au redémarrage suivant, et plus rien dans les journaux ne relie l’effet à sa cause.

Un programme est un texte d’instructions, écrit à l’avance et exécuté ensuite. Mais certains langages de programmation permettent aussi de fabriquer des instructions pendant l’exécution, puis de les exécuter aussitôt. C’est pratique : on peut ainsi laisser l’utilisateur écrire une formule de calcul, une règle de tri, un modèle de courrier.

Le danger apparaît quand le programme fabrique ces instructions en y recopiant ce que l’utilisateur a saisi. L’utilisateur peut alors écrire non plus une donnée, mais des instructions de son choix, que le programme exécutera comme si leur auteur en était le développeur. Il ne s’agit plus de lire des informations auxquelles on n’a pas droit : il s’agit de faire faire au programme ce qu’on veut.

C’est le défaut que le catalogue CWE appelle CWE-94, « injection de code ». Il est proche d’un autre, CWE-78, mais il faut les distinguer : dans CWE-78, l’attaquant fait exécuter une commande par le système d’exploitation ; ici, il fait exécuter des instructions par l’application elle-même, dans son propre langage.

La variante la plus fréquente aujourd’hui concerne les modèles de documents. Un modèle est un texte à trous, dans lequel le programme vient remplacer les trous par des valeurs. Si l’utilisateur peut modifier non pas les valeurs mais le texte à trous, il écrit dans le petit langage du modèle, qui donne souvent accès à tout le reste.

La bonne pratique consiste à ne jamais fabriquer d’instructions à partir d’une saisie. Quand une application doit vraiment offrir cette souplesse, elle doit faire tourner ce code dans un espace confiné, aux droits réduits et au temps limité : non pour empêcher l’abus, qui devient inévitable, mais pour qu’il ne sorte pas de la pièce.

À lire aussi

Pages voisines

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