Les requêtes préparées pour prévenir certaines injections SQL

Lorsqu'une requête SQL doit utiliser une valeur tirée d'une variable, nous exposons notre base de données aux injections SQL. Il s'agit d'une technique utilisée par les « hackers » pour tenter d'obtenir des informations sensibles tirées de la base de données.

Les variables utilisées dans la requête pourraient tirer leur valeur d'un formulaire Web ($_POST), d'un paramètre dans l'URL ($_GET), d'un cookie ($_COOKIE), etc. Donc, si nous ne prenons pas nos précautions, un utilisateur malveillant pourrait entrer comme valeur des caractères particuliers qui modifieraient le comportement de la requête. Il pourrait ainsi obtenir des accès sans connaître le mot de passe, modifier les données stockées dans la BD, etc.

C'est pourquoi, lorsqu'une requête contient une valeur tirée d'une variable, nous utiliserons toujours des requêtes préparées, aussi appelées requêtes paramétrables, afin de protéger nos données contre les injections SQL.

▼Publicité

Notez que si votre requête ne contient aucune valeur tirée d'une variable PHP, il est préférable de ne pas utiliser les requêtes préparées.

Mise à jour

J'ai ajusté les exemples afin de rendre la gestion des exceptions plus visible.

Structure du code

Votre programme devra utiliser la structure de code suivante. De nombreux commentaires ont été ajoutés pour vous aider à bien comprendre chacune des étapes.

Syntaxe PHP

// 1. Prépare la requête en plaçant un ? à la place de chaque paramètre (variable utilisée dans la requête).

//    Il est d'usage d'utiliser une variable nommée stmt (StaTeMenT).

//    Remarquez qu'avec les requêtes préparées, PHP s'occupera d'ajouter lui-même les apostrophes de chaque côté d'une variable string.

$requete = "SELECT champ1, champ2, champ3 WHERE champ4=? OR champ5=?";

$stmt = $mysqli->prepare($requete);

 

if ($stmt) {

 

    // 2. Indique le type de chacun des paramètres : string (s), integer (i) ou decimal (d).

    //    Assigne ensuite à chacun des paramètres, dans l'ordre, la variable contenant sa valeur. 

    $stmt->bind_param("xx", $var1, $var2);

 

    // 3. Exécute la requête. 

    $stmt->execute();

    // Sans cette ligne, il ne sera pas possible de connaître le nombre de lignes retournées par un SELECT.

    $stmt->store_result(); 

 

    if ($stmt->affected_rows == -1) {

        // Exécution non réussie. 

        // Arrivera ici si les paramètres posent problème (ex : contrainte d'intégrité référentielle non respectée).

        // Ce bloc est important seulement pour les UPDATE, INSERT ou DELETE.

        ...

        echo_debug($mysqli->error);

    }

    else if ($stmt->affected_rows == 0) {

        // Pour une requête SELECT, la documentation suggère l'utilisation de $stmt->num_rows même si $stmt->affected_rows fonctionne également.

        // Aucun enregistrement ne correspond à la requête.

        ...

    }

    else {

        // 4. Fait le lien entre la position des champs lus par le SELECT et les variables qui seront initialisées lors du fetch.

        //    Cette étape n'aura pas lieu si la requête était un INSERT, un UPDATE ou un DELETE.

        $stmt->bind_result($champ1, $champ2, $champ3);

 

        // 5. Retrouve les données de chacune des lignes de résultats.

        while ($stmt->fetch()) {

            ...

        }

    }

 

    // 6. Libère la mémoire (doit être fait à la fin du if ($stmt)).

    $stmt->close();

}

else {

    // Arrivera ici s'il y a une erreur dans la requête (ex : mauvais nom de champ).

    ...

    echo_debug($mysqli->error);

}

Exemples

Voici un exemple concret pour une requête SELECT :

PHP

$ville = '';

$annee = -1;

 

if (isset($_GET['ville']) {

    $ville = strtoupper($_GET['ville']);   // dans cet exemple, on convertit en majuscules puisqu'il y a un UPPER dans la requête

}

if (isset($_GET['ville']) {

    $annee = $_GET['annee'];

}

 

$requete = "SELECT client_id, client_prenom, client_nomfamille FROM client WHERE UPPER(client_ville)=? AND EXTRACT(year FROM client_naissance) = ORDER BY client_nomfamille, client_prenom";

$stmt = $mysqli->prepare($requete);

 

if ($stmt) {

 

    $stmt->bind_param("si", $ville, $annee);

 

    $stmt->execute();

    $stmt->store_result();

 

    if ($stmt->num_rows == 0) {

        echo "<div class='messageavertissement'>Il n'y a aucun client né en $annee dans la ville $ville.</div>";

    }

    else {  

        $stmt->bind_result($client_id, $client_prenom, $client_nomfamille);

 

        echo "<table>";

 

        while ($stmt->fetch()) {

            echo "<tr><td><a href='detailsclient.php?client_id=$client_id'>Détails</a></td><td>$client_nomfamille</td><td>$client_prenom</td></tr>";         

        }

 

        echo "</table>";

    } 

 

    $stmt->close();

}

else {

    echo "<div class='messageerreur'>Nous sommes désolés, un problème technique nous empêche de retrouver les données.</div>";

    echo_debug($mysqli->error);

}

Cet autre exemple illustre l'utilisation d'une requête préparée lors d'un INSERT :

PHP

$prenom = '';

$nomfamille = '';

 

// Retrouve les données du formulaire.

if (isset($_POST['prenom']) {

    $prenom = $_POST['prenom'];

}

if (isset($_POST['nomfamille']) {

    $nomfamille = $_POST['nomfamille'];

}

 

// Valide les données.

$message = "";

 

if ("" == $prenom) {

    $message .= "Le prénom est requis.<br />";

}

if ("" == $nomfamille) {

    $message .= "Le nom de famille est requis.<br />";

}

 

if ($message == "") {

 

    // Tente l'enregistrement des données.

    $requete = "INSERT INTO CLIENT(client_prenom, client_nomfamille) VALUES(?, ?)";

    $stmt = $mysqli->prepare($requete);

 

    if ($stmt) {

 

        $stmt->bind_param("ss", $prenom, $nomfamille);

 

        $stmt->execute();

 

        if ($stmt->affected_rows == -1) {

            echo "<div class='messageerreur'>Nous sommes désolés, un problème technique nous empêche d'enregistrer le client (code 1).</div>";

            echo_debug($mysqli->error);

        }

        else {  

            echo "<div class='messageinformation'>Le client a été ajouté avec succès !</div>";

        }

 

        $stmt->close();

    }

    else {

        echo "<div class='messageerreur'>Nous sommes désolés, un problème technique nous empêche d'enregistrer le client (code 2).</div>";

        echo_debug($mysqli->error);

    }

}

else {

    echo "<div class='messageerreur'>$message</div>";

}

Pour plus d'information

« Les requêtes préparées ». PHP. http://php.net/manual/fr/mysqli.quickstart.prepared-statements.php

Merci de partager ! Share on FacebookTweet about this on TwitterShare on Google+Share on LinkedInPin on PinterestShare on StumbleUponEmail this to someone
Catégories

2 commentaires