Neutraliser les injections SQL
Objectif : Exécuter les recherches et modifications de contacts avec des requêtes PDO paramétrées.
Une recherche construite en concaténant la saisie du visiteur peut transformer une valeur de recherche en commande SQL. Une modification construite de la même façon expose aussi les données du carnet. Vous allez faire en sorte que PDO transmette les valeurs séparément de la requête, afin qu’elles soient traitées comme des données.
Le dépôt reste responsable de la persistance ; les distinctions entre les couches ont été établies dans « Définir les responsabilités des couches ». Dans cette leçon, concentrez-vous sur la forme des requêtes exécutées par ContactDepot et sur les valeurs qu’elles reçoivent. Les exemples utilisent les colonnes id, nom, courriel et telephone du carnet.
Pourquoi séparer la requête de ses valeurs
Une requête préparée contient d’abord une instruction SQL avec des marqueurs. Vous fournissez ensuite les valeurs à PDO, qui les transmet comme paramètres et non comme fragments de SQL. Évitez donc de fabriquer une requête en insérant directement une saisie utilisateur dans une chaîne, même si vous pensez pouvoir filtrer les caractères dangereux.
<?php
declare(strict_types=1);
$pdo = new PDO(
'sqlite:' . __DIR__ . '/contacts.sqlite',
options: [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
);
$recherche = trim((string) ($_GET['recherche'] ?? ''));
$sql = <<<'SQL'
SELECT id, nom, courriel, telephone
FROM contacts
WHERE nom LIKE :recherche
OR courriel LIKE :recherche
ORDER BY nom
SQL;
$requete = $pdo->prepare($sql);
$requete->execute(['recherche' => '%' . $recherche . '%']);
$contacts = $requete->fetchAll();
print_r($contacts);Pour une recherche correspondant à « Alice », la requête renvoie les lignes dont le nom ou le courriel contient « Alice ». La valeur est transmise au marqueur : elle ne peut pas ajouter une clause SQL.Cet exemple autonome suppose une base SQLite contenant la table contacts et son schéma usuel. Dans le projet, gardez la connexion fournie lors de l’assemblage des dépendances et réalisez ces opérations dans src/Depot/ContactDepot.php ; ne créez pas une connexion différente à chaque méthode. Le tableau transmis à execute() associe chaque marqueur nommé à une valeur.
Paramétrer les modifications
Le même principe s’applique à une insertion ou à une mise à jour. Ici, l’identifiant du contact et ses champs sont des paramètres. La structure SQL reste fixe et une valeur contenant des apostrophes ou des caractères ressemblant à du SQL ne devient pas une instruction.
<?php
$sql = <<<'SQL'
UPDATE contacts
SET nom = :nom,
courriel = :courriel,
telephone = :telephone
WHERE id = :id
SQL;
$requete = $pdo->prepare($sql);
$requete->execute([
'nom' => $nom,
'courriel' => $courriel,
'telephone' => $telephone,
'id' => $id,
]);
$contactModifie = $requete->rowCount() === 1;Dans ContactDepot, adaptez les noms des marqueurs aux signatures et aux requêtes déjà présentes, sans modifier le contrat de dépôt pour cette leçon. Le service métier continue de vérifier les règles applicables aux données avant d’appeler le dépôt ; les paramètres PDO empêchent, eux, l’interprétation des valeurs comme du SQL. Ces protections répondent à des risques différents et se complètent.
Sécuriser les éléments SQL dynamiques
Les marqueurs protègent les valeurs, mais ne permettent pas de paramétrer une table, une colonne ou un mot-clé SQL. Si votre application doit proposer plusieurs tris, associez une option reçue à une liste fermée d’expressions connues. N’ajoutez jamais directement la valeur reçue à la chaîne SQL.
<?php
$colonnesDeTri = [
'nom' => 'nom',
'courriel' => 'courriel',
];
$triDemande = (string) ($_GET['tri'] ?? 'nom');
$colonne = $colonnesDeTri[$triDemande] ?? 'nom';
$sql = 'SELECT id, nom, courriel, telephone FROM contacts ORDER BY ' . $colonne;
$contacts = $pdo->query($sql)->fetchAll();Les seules colonnes ajoutées à la requête sont « nom » et « courriel », choisies dans la liste du code. Une valeur de tri inconnue retombe sur « nom ».Pour vérifier le dépôt, recherchez toutes les opérations SQL qui incluent une donnée issue d’une requête HTTP ou d’un formulaire. Pour chacune, vérifiez que la valeur est liée par une requête préparée, et que les éventuels éléments structurels dynamiques proviennent d’une liste blanche. Vous pouvez ensuite compléter les tests du dépôt ou les tests fonctionnels avec des valeurs contenant des apostrophes et des caractères spéciaux, puis vérifier que la recherche ou la modification reste correcte.
À retenir
- Préparez la requête SQL, puis transmettez les valeurs séparément à PDO.
- Utilisez les paramètres liés pour les recherches, insertions et modifications.
- Les paramètres PDO représentent des valeurs, pas des noms de colonnes ni des mots-clés SQL.
- Pour tout élément SQL dynamique, choisissez parmi une liste blanche définie dans le code.
La prochaine leçon traite de l’encodage des données affichées afin de prévenir les attaques XSS.