Aller au contenu principal

Injection SQL

Comprendre et exploiter cette vulnérabilité

Notions théoriques

L'injection SQL est l'une des attaques les plus courantes et dangereuses en cybersécurité.

Elle permet à un attaquant d'exécuter des requêtes SQL malveillantes sur une base de données en exploitant une faille dans un formulaire ou une URL mal sécurisée.

Fonctionnement de l'injection SQL

Lorsqu'un site Web interagit avec une base de données, il utilise des requêtes SQL pour récupérer, insérer, modifier ou supprimer des données (CRUD).

attention

Si les requêtes SQL ne sont pas correctement sécurisées, un attaquant peut injecter du code SQL malveillant pour manipuler la base de données.

Exemple de vulnérabilité

Prenons un site avec un formulaire de connexion :

SELECT * FROM utilisateurs WHERE username = '$utilisateur' AND password = '$mot_de_passe';

Si l'application insère directement les valeurs de l'utilisateur sans vérification, un attaquant peut entrer :

  • Nom d'utilisateur : admin' --
  • Mot de passe : (vide)

Ce qui donnera la requête suivante :

SELECT * FROM utilisateurs WHERE username = 'admin' --' AND password = '';

Le -- transforme le reste de la requête en commentaire, permettant à l'attaquant de se connecter sans mot de passe.

Types d'injections SQL

  1. Injection classique :

    L'attaquant insère du code SQL directement dans les champs de saisie.

  2. Blind SQL Injection :

    L'attaquant ne voit pas directement les résultats mais peut deviner des informations en analysant les réponses du serveur.

  3. Time-based SQL Injection :

    L'attaquant utilise des requêtes qui ralentissent la réponse du serveur pour détecter des vulnérabilités.

Comment se protéger ?

  • Utilisation des requêtes préparées (Prepared Statements) pour éviter l'injection.
  • Validation des entrées : Vérifier et filtrer les données saisies par l'utilisateur.
  • Utilisation de privilèges limités : Ne pas donner trop de droits aux utilisateurs de la base de données.
  • Désactivation des erreurs SQL visibles : Ne pas afficher les messages d'erreur SQL à l'utilisateur.

Exemple pratique

Exploitation d'une injection SQL

Nous allons voir comment une simple injection SQL peut permettre d'accéder à une base de données non sécurisée.

Contexte

Un site Web possède un formulaire de connexion avec cette requête SQL vulnérable :

SELECT * FROM utilisateurs WHERE username = '$utilisateur' AND password = '$mot_de_passe';

Un attaquant peut essayer de contourner l'authentification en entrant :

  • Nom d'utilisateur : ' OR 1=1 --
  • Mot de passe : (vide)

Cette entrée modifie la requête SQL comme suit :

SELECT * FROM utilisateurs WHERE username = '' OR 1=1 --' AND password = '';

Explication :

  • OR 1=1 est toujours vrai, donc la condition est validée pour tous les utilisateurs.
  • -- transforme le reste en commentaire, supprimant la vérification du mot de passe.

Résultat

L'attaquant est connecté en tant que premier utilisateur de la base de données, souvent un administrateur.


Test de mémorisation/compréhension


Quelle est la principale cause d'une injection SQL ?


Que permet une injection SQL ?


Quel caractère est souvent utilisé pour commenter une requête SQL et contourner la sécurité ?


Quel type d'injection SQL repose sur l'analyse du temps de réponse du serveur ?


Quelle est la meilleure méthode pour éviter l'injection SQL ?


Pourquoi une requête SQL contenant `OR 1=1` est-elle problématique ?


Quel est l'effet d'une injection SQL sur un formulaire de connexion vulnérable ?


Pourquoi ne faut-il pas afficher les erreurs SQL aux utilisateurs ?


Quel rôle joue `--` dans une injection SQL ?


Pourquoi limiter les privilèges des comptes SQL est une bonne pratique ?



TP pour réfléchir et résoudre des problèmes

Objectif

  • Comprendre comment une application vulnérable peut être attaquée via une injection SQL.
  • Apprendre à sécuriser une requête SQL en utilisant PDO avec des paramètres nommés.

1. Création de la base de données et de la table utilisateurs

Tout d'abord, créez une base de données et une table utilisateurs contenant des identifiants de connexion.

Création de la base de données

CREATE DATABASE test_db;
USE test_db;

Création de la table utilisateurs

CREATE TABLE utilisateurs (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
password VARCHAR(255) NOT NULL
);

Insertion de données

Ajoutez quelques utilisateurs pour tester l'authentification :

INSERT INTO utilisateurs (username, password) VALUES ('admin', 'password123');
INSERT INTO utilisateurs (username, password) VALUES ('user1', 'mypassword');

2. Script PHP vulnérable (à ne pas utiliser en production !)

Ce script récupère les entrées de l'utilisateur via l'URL et exécute une requête SQL non sécurisée.

Code PHP vulnérable
<?php
$dsn = "mysql:host=localhost;dbname=test_db;charset=utf8mb4";
$username = "root";
$password = "";

try {
$pdo = new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
} catch (PDOException $e) {
die("Erreur de connexion : " . $e->getMessage());
}

$user = $_GET['username'] ?? '';
$pass = $_GET['password'] ?? '';

$query = "SELECT * FROM utilisateurs WHERE username = '$user' AND password = '$pass'";
$result = $pdo->query($query);

if ($result->rowCount() > 0) {
echo "Connexion réussie !";
} else {
echo "Échec de connexion.";
}
?>
Test d'une injection SQL

Essayez d'accéder à l'URL suivante :

http://localhost/login.php?username=admin'--&password=

Cela contourne l'authentification et connecte l'attaquant en tant qu'admin.

3. Sécurisation avec PDO et paramètres nommés

Modifiez le script pour utiliser une requête préparée avec des paramètres nommés.

Code PHP sécurisé
<?php
$dsn = "mysql:host=localhost;dbname=test_db;charset=utf8mb4";
$username = "root";
$password = "";

try {
$pdo = new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
} catch (PDOException $e) {
die("Erreur de connexion : " . $e->getMessage());
}

$user = $_GET['username'] ?? '';
$pass = $_GET['password'] ?? '';

// Utilisation d'une requête préparée avec des paramètres nommés
$query = "SELECT * FROM utilisateurs WHERE username = :username AND password = :password";
$stmt = $pdo->prepare($query);
$stmt->execute([
'username' => $user,
'password' => $pass
]);

if ($stmt->rowCount() > 0) {
echo "Connexion réussie !";
} else {
echo "Échec de connexion.";
}
?>

4. Explication de la sécurisation

  • Utilisation de prepare() : La requête SQL est préparée à l'avance, empêchant l'injection SQL.
  • Utilisation de execute() avec un tableau associatif : Les valeurs sont traitées comme des données et non comme du code SQL.
  • Protection contre les attaques : Même si un attaquant entre ' OR 1=1 --, la requête ne sera pas modifiée.

5. Test de la sécurité

Essayez à nouveau d'accéder à l'URL :

http://localhost/login.php?username=admin'--&password=

Résultat attendu : L'injection SQL ne fonctionne plus, et la connexion échoue.

6. Amélioration : Hashage des mots de passe

Actuellement, les mots de passe sont stockés en clair.

Pour une meilleure sécurité, utilisez bcrypt avec password_hash() et password_verify().

Modification de l'insertion des utilisateurs
$hashed_password = password_hash('password123', PASSWORD_BCRYPT);
$query = "INSERT INTO utilisateurs (username, password) VALUES (:username, :password)";
$stmt = $pdo->prepare($query);
$stmt->execute([
'username' => 'admin',
'password' => $hashed_password
]);
Modification de la vérification du mot de passe
$query = "SELECT * FROM utilisateurs WHERE username = :username";
$stmt = $pdo->prepare($query);
$stmt->execute(['username' => $user]);
$userData = $stmt->fetch(PDO::FETCH_ASSOC);

if ($userData && password_verify($pass, $userData['password'])) {
echo "Connexion réussie !";
} else {
echo "Échec de connexion.";
}
Points clés
  • Ne jamais insérer directement des entrées utilisateur dans une requête SQL.
  • Toujours utiliser des requêtes préparées avec PDO.
  • Stocker les mots de passe de manière sécurisée avec password_hash().
Bonus
  • Ajoutez un formulaire HTML pour tester l'authentification avec POST au lieu de GET.
  • Implémentez un système de sessions pour gérer la connexion des utilisateurs.