Retour au cours

cyber / cybersecurite-fondamentale

OWASP Top 10 : injection SQL

Leçon 81 exercice

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.

ApprocheFiabilitéPourquoi
Filtrage de caractèresFragileToujours contournable (encodage, casse, variantes)
Requêtes préparées (bind parameters)FiableSépare structurellement code SQL et données
ORM (SQLAlchemy...)Fiable par défautGé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.

python
# 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
python
# 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
python
# 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()
sql
-- 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 --
bash
# 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
python
# 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

1 disponible
1

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.

Résoudre l’exercice →