Formation PUB010 : PHP, 2018 Sécuriser le code

Les requêtes préparées


***** CETTE PAGE EST IMPORTANTE. PLACEZ-LA DANS VOS FAVORIS *****

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 utilisateurs malveillants pour tenter d'obtenir des informations sensibles tirées de la base de données.

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.

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.

▼Publicité

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.

Selon la documentation officielle de PHP, l'exécution d'une requête préparée se déroule en deux étapes : la préparation et l'exécution.

Lors de la préparation, un template de requête est envoyé au serveur de base de données. Le serveur effectue une vérification de la syntaxe, et initialise les ressources internes du serveur pour une utilisation ultérieure.

La préparation est suivie de l'exécution. Pendant l'exécution, le client lie les valeurs des paramètres et les envoie au serveur. Le serveur crée une requête depuis le template et y lie les valeurs pour l'exécution, en utilisant les ressources internes créées précédemment.

Syntaxe PHP

// 1. Prépare la requête en plaçant un ? à la place de chaque paramètre (chaque 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 FROM table1 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 qui contient 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 (0 == $stmt->errno) {

        if ($stmt->num_rows > 0) {

            // Pour une requête INSERT, UPDATE ou DELETE, travailler plutôt avec $stmt->affected_rows

 

            // 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()) {

                ...

            }

        }

        else {

            // aucun enregistrement ne correspond à la requête ou aucun enregistrement n'a été modifié par la requête.

            ...

        }

    }

    else { 

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

        ...

        echo_debug($stmt->error);

    }

 

 

    // 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);

}

Exemple avec requête SELECT

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 id, prenom, nomfamille FROM clients WHERE UPPER(ville)=? AND EXTRACT(year FROM naissance) = ? ORDER BY nomfamille, prenom";

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

 

if ($stmt) {

 

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

 

    $stmt->execute();

    $stmt->store_result();

 

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

        if ($stmt->num_rows > 0) {

            $stmt->bind_result($id, $prenom, $nomfamille);

 

            echo '<table>';

 

            while ($stmt->fetch()) {

                echo "<tr><td><a href='detailsclient.php?id=$id'>Détails</a></td><td>$nomfamille</td><td>$prenom</td></tr>";         

            }

 

            echo '</table>';

 

        }

        else {

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

        } 

    } 

    else {

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

        echo_debug($stmt->error);

    }

 

    $stmt->close();  

}

else {

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

    echo_debug($mysqli->error);

}

Exemple avec INSERT

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 clients(prenom, nomfamille, actif) VALUES(?, ?, 1)";

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

 

    if ($stmt) {

 

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

 

        $stmt->execute();

 

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

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

        }

        else {

            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($stmt->error);

        }

 

        $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>";

}

Exemple avec DELETE

Voici finalement un exemple d'utilisation avec un DELETE :

PHP

// Retrouve l'information dans l'URL.

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

    $id = $_GET['id'];

}

else {

    $id = -1;

}

   

// Tente la suppression des données.

$requete = "DELETE FROM clients WHERE id=?";

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

 

if ($stmt) {

   

    $stmt->bind_param('i', $id);

 

    $stmt->execute();

 

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

        // si l'id n'existe pas, ça ne génère pas d'erreur mais ça ne supprime rien.

        if ($stmt->affected_rows > 0) {

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

        }

        else {

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

            echo_debug("Id de client non trouvé pour la suppression : $id");

        }

    }

    else {

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

        echo_debug($stmt->error);

    }

 

    $stmt->close();

}

else {

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

    echo_debug($mysqli->error);

}

 

Pour plus d'information

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

Dernière révision le 28 octobre 2019
Merci de partager !

Site fièrement hébergé chez A2 Hosting.

Soumettre