Aller au contenu

CWE-89 : injection SQL, la classe de la requête concaténée

CWE-89 : injection SQL, la classe de la requête concaténée

Section intitulée « CWE-89 : injection SQL, la classe de la requête concaténée »

Deuxième du CWE Top 25 2025, et il faut une certaine constance pour rester sur le podium d’un classement de défauts évitables un quart de siècle après avoir été documentée. Le libellé officiel est Improper Neutralization of Special Elements used in an SQL Command (‘SQL Injection’) : « neutralisation incorrecte d’éléments spéciaux utilisés dans une commande SQL ». Le MITRE accepte les deux abréviations usuelles, SQL injection et SQLi.

→ Lire l’explication complète de la marmotte

Le produit construit tout ou partie d’une commande SQL à partir d’une entrée influencée depuis l’extérieur, et ne neutralise pas (ou neutralise mal), les éléments spéciaux qui pourraient modifier la commande prévue. La définition est précise sur deux points qu’on néglige.

« Tout ou partie » : la classe n’exige pas que l’attaquant écrive la requête entière. Un seul fragment contrôlé suffit, et c’est généralement le cas le plus difficile à repérer, parce que la requête ressemble encore à ce que le développeur avait en tête.

« Influencée depuis l’extérieur » : l’entrée ne vient pas nécessairement d’un formulaire. Un en-tête HTTP, un nom de fichier téléversé, une valeur relue dans la base et réinjectée dans une seconde requête, un champ synchronisé depuis un annuaire : toutes ces sources sont « externes » au sens du catalogue. L’injection SQL dite de second ordre, où la donnée piégée est d’abord stockée proprement puis réutilisée sans échappement, relève de cette même entrée.

En revanche, la classe ne couvre pas l’exposition d’une base de données sans mot de passe, ni l’octroi de droits trop larges à un compte applicatif, ni l’absence de chiffrement des données sensibles. Ces trois défauts accompagnent souvent une injection dans un rapport d’audit, mais ils portent d’autres numéros. Confondre les quatre produit le genre de ticket qui demande « corriger l’injection SQL » à une équipe qui ne sait pas lequel des quatre problèmes elle doit traiter.

CWE-89 est une base, de statut stable, ce qui signifie que sa définition ne bouge plus. Son parent dans la vue de recherche est la classe CWE-943, « neutralisation incorrecte d’éléments spéciaux dans une logique d’interrogation de données », qui regroupe toutes les injections visant un langage de requête : SQL, mais aussi LDAP, XPath ou les langages d’interrogation des bases non relationnelles. Dans la vue simplifiée que beaucoup d’outils utilisent, son parent est directement CWE-74, le tronc commun de toutes les injections.

Une seule variante descend d’elle : CWE-564, l’injection SQL à travers Hibernate, c’est-à-dire par la couche de correspondance objet-relationnel censée précisément protéger du problème. Le catalogue garde ainsi la trace d’un enseignement utile : un ORM n’immunise pas, il déplace l’endroit où l’on se trompe.

Deux voisines méritent d’être distinguées. CWE-20, la validation insuffisante des entrées, est une classe plus haute et plus vague : un rapport qui code une injection SQL en CWE-20 n’a pas tort, il est seulement inutilement imprécis, c’est exactement le genre d’attribution trop abstraite que le MITRE a remappée pour un quart des vulnérabilités de l’édition 2025. Et CWE-943, le parent, sert quand le langage de requête visé n’est pas SQL : une injection LDAP mal classée en CWE-89 fait croire à une base de données là où il y a un annuaire.

Parce que la méthode de détection est triviale et la correction, structurelle.

Un scanner envoie une apostrophe. Si le serveur répond par une erreur de syntaxe SQL, le verdict est immédiat ; s’il ne répond rien, la variante aveugle temporelle permet encore de conclure en mesurant des délais. Cette facilité de détection alimente un flux continu de vulnérabilités publiées, donc de points dans un classement bâti sur ce flux.

La correction, elle, demande de changer la manière d’écrire les requêtes partout : passer de la concaténation aux requêtes paramétrées, dans chaque module, y compris le script de reprise écrit en 2014 par quelqu’un qui n’est plus là. Les familles de produits les plus représentées sont celles où ce travail n’a jamais été fait en entier : systèmes de publication et leurs greffons, applications métier internes, outils d’analyse de données exposés sur Internet. L’affaire française de 2026 en donne l’illustration : une injection SQL non authentifiée dans Metabase, numérotée CVE-2026-72898, a compromis neuf instances ministérielles, la classe au rang 2 du classement, rencontrée dans un produit d’analyse, sur des serveurs dont personne ne tenait l’inventaire.

Le site traite l’exploitation et les défenses ailleurs, et il n’y a pas lieu de les répéter : l’injection SQL aveugle temporelle montre comment une base muette parle quand même, et JavaScript et SQL côté web explique pourquoi la concaténation de chaînes est la mauvaise manière de construire une requête, et à quoi ressemble la bonne.

Côté exploitation, installer et sécuriser PostgreSQL couvre les deux contrôles qui limitent les dégâts quand l’injection a réussi : un compte applicatif aux droits strictement nécessaires, et des journaux qui permettent de reconstituer ce qui a été lu. Côté chaîne de développement, les tests SAST et DAST trouvent les requêtes concaténées avant la mise en production, ce qui est la seule façon économique de traiter le problème à l’échelle d’un dépôt.

Dans le Top 10 web de l’OWASP, cette faiblesse se lit sous la catégorie des injections, fusionnée avec le cross-site scripting depuis 2021 : deux classes CWE distinctes, un seul code OWASP.

Les requêtes paramétrées règlent le problème à la racine : la commande et les données voyagent séparément, le moteur ne peut plus confondre les deux. Tout le reste est du rattrapage.

Quand une requête doit vraiment être construite dynamiquement (un nom de colonne de tri, un ordre, une liste de champs), le paramétrage ne s’applique pas, et il faut alors une liste blanche de valeurs autorisées, jamais un échappement maison. C’est le seul cas légitime, et il tient en quelques lignes par application ; au-delà, c’est qu’on a reproduit le problème.

Enfin, l’accès applicatif doit être le plus étroit possible : un compte en lecture seule sur les tables nécessaires transforme une injection réussie en fuite de données, ce qui est grave, plutôt qu’en prise de contrôle du serveur, ce qui est pire.

Une équipe qui ne nomme pas la classe traite l’injection comme un incident isolé : le paramètre signalé passe en requête paramétrée, le ticket se ferme, et les quatre-vingts autres requêtes concaténées du même dépôt restent en place, écrites selon la même habitude. Six mois plus tard, un scan trouve la suivante, et le cycle recommence au prix d’un correctif par découverte. Le coût ne se mesure pas en correctifs mais en portée : une seule injection non authentifiée sur un service exposé donne à l’attaquant la lecture de tables entières, souvent des identifiants réutilisés ailleurs, parfois l’écriture, et toujours un accès qui ressemble dans les journaux à un usage normal de l’application. Quand personne n’a mesuré l’étendue du défaut dans le code, personne ne peut répondre à la seule question qui compte après l’incident : où ailleurs est-ce encore possible ?

La plupart des applications conservent leurs informations dans une base de données, et leur parlent dans un langage appelé SQL. Une commande SQL ressemble à une phrase : « donne-moi la fiche du client dont le nom est Durand ».

Le programme construit cette phrase au moment où il en a besoin, en y insérant ce que l’utilisateur a demandé. S’il se contente de recopier la demande dans la phrase, un utilisateur malveillant peut y ajouter de la grammaire SQL plutôt que du texte : au lieu d’écrire un nom, il écrit un nom suivi d’une autre instruction. La base de données, qui ne distingue pas ce que le programmeur voulait dire de ce que la phrase dit, exécute l’ensemble.

C’est le défaut que le catalogue CWE appelle CWE-89, et qu’on nomme « injection SQL ». Il permet selon les cas de lire des informations auxquelles on n’a pas droit, de les modifier, de les effacer, et parfois de prendre le contrôle du serveur.

La réparation est connue depuis les années 1990 et tient en une règle : ne jamais fabriquer la phrase en recopiant la demande. On envoie à la base la phrase d’un côté, avec des emplacements vides, et les valeurs de l’autre ; la base remplit les emplacements sans jamais y chercher de grammaire. Ce procédé s’appelle une requête paramétrée. Si la solution est si simple et le défaut si ancien, pourquoi arrive-t-il deuxième du classement annuel ? Parce que corriger une application revient à vérifier chacune des centaines d’endroits où elle parle à sa base, y compris ceux qu’on a hérités de développeurs partis depuis longtemps, et que personne ne finance ce genre d’inventaire avant l’incident.

À lire aussi

Pages voisines

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