Apprendre PHP & MySQL : Injection SQL

L'injection SQL est une incontournable faille de sécurité web qu'il est important de comprendre afin de s'en prémunir.

Icône de calendrier
Intermédiaire
12 chapitres

Qu’est-ce qu’une injection SQL ?

L’injection SQL est une attaque qui consiste à falsifier des requêtes SQL afin de pirater une base de données.

Cette injection est rendue possible en écrivant du code SQL malicieux dans les entrées utilisateurs.

Pour plus d’informations, je vous invite à consulter le guide dédié aux injections SQL.

Comment se protéger des injections SQL en PHP ?

Pour prémunir toute injection SQL, la règle d’or consiste à sécuriser les requêtes SQL contenant des saisies utilisateurs.

Considérons cette requête SQL recherchant en base de données tous les articles de blog contenant un terme spécifique dans le titre :

SELECT * FROM articles WHERE title LIKE '%" . $keyword . "%'

❌ Danger de la concaténation

Côté serveur nous pourrions implémenter notre moteur de recherche de la manière suivante :

copié !
$db = new PDO('mysql:host=localhost;dbname=ma_bdd;charset=utf8', 'root', '');

if (!empty($keyword)) {
	// ⚠️ Concaténation classique sans requête préparée = DANGER
	$sql = "SELECT * FROM articles WHERE title LIKE '%" . $keyword . "%'";
	$query = $db->query($sql);
	$articles = $query->fetchAll();
	// ...
}

S’il est tentant de chercher à dynamiser sa requête SQL en concaténant directement les entrées de l’utilisateur, c’est en réalité la porte ouverte aux injections SQL.

Imaginez que l’utilisateur saisisse dans le moteur de recherche le mot-clé '; truncate table users; -- '… notre serveur exécutera la requête :

SELECT * FROM articles WHERE title LIKE ''; truncate table users; -- ''

Et là, c’est le drame, tous les utilisateurs de la base de données sont supprimés.

Pour éviter cela, on écrira des requêtes préparées.

✅ Écrire des requêtes préparées

Écrire une requête préparée consiste à rajouter une étape de sécurisation des entrées utilisateur, afin de se prémunir de tout code SQL malveillant avant l’exécution des requêtes.

prepare() et execute()

Pour cela, la classe PDO nous offre une implémentation toute prête de cette mécanique sécurisée via les méthodes prepare() et execute().

prepare() prépare la requête SQL. Cette méthode prend en paramètre la requête SQL au sein de laquelle sont définis des marqueurs (préfixés par :), identifiant les données dynamiques.

copié !
$sql = "SELECT * FROM articles WHERE title LIKE :keyword";
$stmt = $db->prepare($sql);

execute() exécute la requête SQL. Cette méthode prend en paramètre un tableau associatif rattachant une valeur à chaque marqueur.

copié !
$sql = "SELECT * FROM articles WHERE title LIKE :keyword";
$stmt = $db->prepare($sql);
$stmt->execute([':keyword' => "%" . $keyword . "%"]);

Ainsi, PDO se charge d’échapper les caractères indésirables, avant d’insérer les variables dans la requête SQL.

On dit qu’il « prépare » la requête.

bindParam() et bindValue()

Notez que les méthodes bindParam() et bindValue() permettent de rattacher des valeurs aux marqueurs de la requête autrement qu’en les définissant en paramètres de la méthode execute().

Avec bindParam() :

copié !
$sql = "SELECT * FROM articles WHERE title LIKE :keyword";
$stmt = $db->prepare($sql);
$stmt->bindParam(':keyword', "%" . $keyword . "%");

// bindParam : si $keyword change ici, alors la valeur liée au marqueur change.

$stmt->execute();

Avec bindValue() :

copié !
$sql = "SELECT * FROM articles WHERE title LIKE :keyword";
$stmt = $db->prepare($sql);
$stmt->bindValue(':keyword', "%" . $keyword . "%");

// bindValue : si $keyword change ici, alors la valeur liée au marqueur ne change pas.

$stmt->execute();

Quelle différence y a-t-il entre bindParam() et bindValue() ?

bindParam() lie une variable à un paramètre de requête, tandis que bindValue() lie une valeur à un paramètre de requête.