cyber / cybersecurite-fondamentale
OWASP Top 10 : injection SQL
Explication
Ce que vous allez apprendre
- Comprendre pourquoi l'injection SQL survient dès que le code et les données se mélangent dans une requête
- Reconnaître un payload d'injection SQL classique (
' OR '1'='1, commentaire--) - Corriger une injection avec des requêtes préparées (paramètres liés), pas par du filtrage de caractères
- Expliquer pourquoi un ORM comme SQLAlchemy protège par défaut, et quand ce filet de sécurité disparaît
- Connaître le cadre légal strict d'utilisation d'un outil comme sqlmap
Dans quel contexte ?
Un formulaire de connexion sur un site vérifie l'identifiant avec la requête SELECT * FROM users WHERE username = ' + saisie utilisateur + '. Un attaquant qui saisit admin' -- dans le champ nom d'utilisateur transforme la requête en SELECT * FROM users WHERE username = 'admin' -- ' : tout ce qui suit -- est ignoré, y compris la vérification du mot de passe. C'est l'exemple le plus classique d'injection SQL, encore présent aujourd'hui sur des formulaires mal protégés.
Première étape : un changement de terrain. Après les fondations sur l'authentification et la cryptographie, le cours attaque maintenant l'OWASP Top 10, la liste de référence des vulnérabilités web les plus critiques et les plus fréquentes. On commence par l'injection SQL, historiquement la plus dévastatrice de toutes.
Deuxième étape : comprendre l'idée de base, très simple. Une base de données ne fait pas naturellement la différence entre des "instructions" et des "données" si le code de l'application les mélange sans précaution. Quand une entrée utilisateur est collée directement dans le texte d'une requête SQL, l'utilisateur peut écrire du texte qui change le SENS de la requête, au lieu d'en être un simple paramètre.
Pour bien comprendre, une image simple. Imaginez un formulaire papier où quelqu'un pourrait écrire des instructions administratives dans le champ "nom", et où le bureaucrate qui le lit les exécuterait sans se poser de question. C'est exactement ce mécanisme qu'exploite l'injection SQL.
Troisième étape : pourquoi filtrer les caractères dangereux ne suffit pas. On pourrait croire qu'il suffit de bloquer certains caractères comme les guillemets. C'est une approche fragile et toujours contournable, car il existe trop de façons différentes d'écrire la même attaque.
Voici la vraie solution : les requêtes préparées. L'idée n'est plus de filtrer, mais de séparer structurellement le code SQL des données envoyées. Avec les paramètres liés, le moteur de base de données sait dès le départ que telle partie de la requête est un texte, jamais une instruction, même si ce texte contient des guillemets ou des mots-clés SQL.
| Approche | Fiabilité | Pourquoi |
|---|---|---|
| Filtrage de caractères | Fragile | Toujours contournable (encodage, casse, variantes) |
| Requêtes préparées (bind parameters) | Fiable | Sépare structurellement code SQL et données |
| ORM (SQLAlchemy...) | Fiable par défaut | Génère des requêtes paramétrées automatiquement |
Quatrième étape : et les ORM dans tout ça ? Un ORM comme SQLAlchemy protège par défaut, car il génère lui-même des requêtes paramétrées sans qu'on ait à y penser. Le risque revient uniquement si un développeur retombe dans l'écriture de SQL brut et y réinjecte des données par simple concaténation de texte.
Piège fréquent avant de continuer
Les outils comme sqlmap sont des outils d'audit parfaitement légitimes, mais leur usage est strictement encadré par la loi. On ne les utilise que sur un système qu'on possède soi-même, ou pour lequel on a une autorisation écrite explicite. Cette règle vaudra pour toute la partie pentest du cours, retenez-la bien.
Maintenant que vous savez protéger une base de données, la prochaine leçon s'attaque à une cible différente mais avec la même logique de fond : tromper non plus la base de données, mais le navigateur de la victime. C'est le XSS.
Commandes & code
OWASP Top 10 : injection SQL
L'injection SQL survient quand une entrée utilisateur non filtrée est concaténée directement dans une requête SQL, permettant à l'attaquant d'altérer la logique de la requête.
# VULNÉRABLE : concaténation directe de l'entrée utilisateur
def get_user_vulnerable(username: str, db_connection):
query = f"SELECT * FROM users WHERE username = '{username}'"
return db_connection.execute(query)
# Payload d'exemple (en LAB uniquement, jamais sur un système sans autorisation) :
# username = "admin' -- "
# Requête résultante : SELECT * FROM users WHERE username = 'admin' -- '
# Le "--" commente la suite -> contourne la vérification du mot de passe si mal codée# Autre payload classique de contournement d'authentification (démonstration pédagogique en lab)
# username = "' OR '1'='1"
# Requête résultante : SELECT * FROM users WHERE username = '' OR '1'='1'
# '1'='1' est toujours vrai -> retourne potentiellement TOUS les utilisateurs# CORRECT : requêtes préparées avec paramètres liés (bind parameters)
# Le driver sépare le code SQL des données -> l'injection devient structurellement impossible
import sqlite3
def get_user_safe(username: str, db_connection: sqlite3.Connection):
query = "SELECT * FROM users WHERE username = ?"
return db_connection.execute(query, (username,)).fetchone()
# Avec SQLAlchemy (ORM utilisé par le backend Technologik)
from sqlalchemy import text
def get_user_safe_sqlalchemy(username: str, session):
result = session.execute(text("SELECT * FROM users WHERE username = :username"), {"username": username})
return result.fetchone()
# Avec l'ORM directement (le plus sûr, aucune requête brute à écrire)
def get_user_orm(username: str, session, User):
return session.query(User).filter(User.username == username).first()-- Injection SQL en UNION : exfiltrer des données d'une autre table via un champ de recherche
-- Payload de démonstration en lab autorisé sur un endpoint vulnérable /search?q=
' UNION SELECT username, password_hash, NULL FROM users --
-- Injection SQL en aveugle (blind), booléenne : déduire des données bit par bit selon le comportement de la réponse
' AND (SELECT SUBSTRING(password,1,1) FROM users WHERE username='admin')='a' --
-- Injection SQL temporelle (time-based blind) : mesurer un délai pour déduire une condition
'; SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END --# Outil de test en lab autorisé : sqlmap automatise la détection/exploitation d'injections SQL
# UNIQUEMENT contre une cible que vous possédez ou pour laquelle vous avez une autorisation écrite
sqlmap -u "http://lab.local/search?q=test" --batch --level=2 --risk=1 --dbs
sqlmap -u "http://lab.local/search?q=test" --batch -D technologik_lab --tables# Défense en profondeur au-delà des requêtes préparées
def validate_search_input(q: str) -> str:
if len(q) > 200:
raise ValueError("entrée trop longue")
# Liste blanche de caractères si le format attendu est connu (ex: recherche alphanumérique)
import re
if not re.fullmatch(r"[\w\s\-À-ÿ]{1,200}", q):
raise ValueError("caractères non autorisés")
return q
# Principe du moindre privilège : le compte DB applicatif n'a pas de droit DROP/ALTER (voir leçon 4)
# WAF (Web Application Firewall) en défense complémentaire, jamais en remplacement du code sécuriséRésumé
- L'injection SQL vient de la concaténation de données non fiables dans une requête.
- Les requêtes préparées (bind parameters) éliminent la classe de vulnérabilité, pas juste la mitigent.
- Un ORM bien utilisé (SQLAlchemy) protège par défaut ; le risque réapparaît avec
text()mal paramétré. - sqlmap est un outil d'audit légitime uniquement en lab autorisé ou avec mandat explicite.
Exercices pratiques
Mission : le formulaire de recherche qui fuit la base clients
Objectif : Diagnostiquer une injection SQL à partir d'un comportement observé, puis corriger le code vulnérable par des requêtes préparées.
Contexte
L'équipe support de Technologik reçoit un signalement bizarre : quand un client tape test' -- dans la barre de recherche du catalogue de cours, la page renvoie TOUS les cours au lieu d'aucun résultat. Le code du endpoint /search construit sa requête SQL par f-string, comme dans l'exemple vulnérable du cours. Tu dois comprendre pourquoi ce payload précis change le comportement, puis corriger le endpoint.