Objet PDO et protection contre les injections SQL

Publié le : 16-01-2019 11:02

Introduction

L'idée de cet article est de permettre aux plus néophytes d'entre nous de parvenir à créer un code qui ne sera pas perméable aux injections SQL. Ne sera pas détaillée la mise en oeuvre des injections au-delà du strict nécessaire à la compréhension.

Les injections SQL (SQLi)

La faille SQLi est un ensemble de techniques qui permettent à un attaquant, d'utiliser une faiblesse dans le code développé, avec pour objectif de détourner l'utilisation prévue d'une base de données. La finalité étant d'injecter tout ou fragment d'une requête SQL dans la requête qui doit normalement s'exécuter à un instant T de l'application (exemple : identification, suppression de compte, etc).

Typiquement toute exécution d'une requête qui attribue directement des valeurs par concaténation et/ou qui utilise directement des variables provenant directement du front sans aucun traitement crée cette faille dans son code.

$Pdo_Object->query('SELECT * FROM users WHERE login= ' . $_POST['login'] . ' AND password='. $_POST['password'] .');

Si un utilisateur mal intentionné assigne à la valeur $_POST['login'] la valeur : "admin';--" alors la requête SQL qui s'exécutera ne tiendra pas comptes du mot de passe.

L'objet PHP Data Object (PDO)

L'objet PDO est une classe qui permet de s'interfacer avec une base de données pour communiquer avec elle.

Définition du pilote de SGBD

Pour définir le pilote à utiliser (MySql,PostGr,etc) il suffit de se rendre dans le fichier php.ini et d'ajouter (ou de modifier) la ligne extension (ici pour mySql)

extension = php_pdo_mysql.dll 

Constructeur de l'objet PDO

Pour initialiser notre objet dans le code Php (à noter que la chaine de connexion dépend du moteur de sgbd utilisé).

On lui soumet les différentes informations :

  1. host : l'adresse ip ou l'adresse dns du serveur cible hébergeant la base.
  2. dbname : le nom de la base que l'on souhaite interroger.
  3. user : à remplacer par votre profil utilisateur
  4. password : à remplacer par le mot de passe du profil utilisateur utilisé
  5. PDO::ATTR_ERRMODE : demande une erreur typée PDOException si une erreur survient
$Pdo_Object = new PDO("mysql:host=127.0.0.1;dbname=DataBase","user","password",array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION )); 

Inutile d'ouvrir la connexion

L'objet PDO ne nécessite pas d'ouvrir explicitement la connexion à la base de données avant de pouvoir communiquer avec elle. Cette connexion étant nativement établie à partir de l'initialisation de l'objet.

Fermer la connexion

Pour fermer la connexion à la base de données il suffit de rendre null l'objet PDO précédemment créer.

$Pdo_Object = null;

Les requêtes

Pour exécuter une requête "classique", on déclare notre requête dans l'objet PDO qui se charge d'aller récupérer les résultats (query() appelant implicitement la fonction execute()) enfin on effectue une récupération grâce à fetch puis on utilise les données.

try{
   $Request = $Pdo_Object->query('SELECT id, title,author,date,isbn FROM books');

   while($Book = $Request->fetch())
   {
     //Faire quelque choses des données

   }
}
catch(PDOException $pdo_e){
  //Faire quelque choses en cas d'erreur PDO
}
catch(Exception $e){
  //Pour les autres erreurs faire autre chose 
}

Les requêtes "préparées"

Les requêtes préparées offrent une couche de sécurité supplémentaire car elles effectuent les contrôles nécessaires à la bonne sécurisation des données envoyées en paramètres. D'après la documentation PHP : "si votre application utilise exclusivement les requêtes préparées, vous pouvez être sûr qu'aucune injection SQL n'est possible". Vu qu'aucune information n'est précisée et bien que la sécurité de ces requêtes n'est plus à démontrer, on est sur une "boîte noire" il convient donc d'appliquer les contrôles de bases en amont.

On commence donc par sécuriser les paramètres provenant de l'utilisateur (ce processus sera par la suite détaillé dans un autre article). Puis on contrôle que le format de la donnée une fois nettoyer correspond bien au format attendu on lève une erreur en cas format discordant.

Maintenant pour les requêtes préparées le processus est légèrement différent.

  1. On commence par créer un tableau associatif qui contient des couples clés=>valeur.
  2. Puis on écrit notre requête SQL. En assignant les "clés" aux emplacements que nous souhaitons, en prenant le soin des pré-fixers de deux points (ex : :target_isbn).
  3. Encore une fois on soumet la requête à l'objet PDO mais par la fonction prepare().
  4. Puis via l'appel de la fonction execute() on déclenche la sélection (nb : marche aussi avec des requêtes d'altérations : insert, update, delete, etc.)

 

try{ 
   $Isbn = Clear_Front_Variable($_GET['isbn']);
   $Allow = Valid_Isbn_Format($Isbn);

   if(!$Allow) throw new Exception("Le format de l'isbn ne correspond pas au format attendu");

   $Arr_Key_Value = array('target_isbn' => $Isbn);
   $Sql_Query = "SELECT id,title,author,date,isbn FROM books WHERE isbn=:target_isbn";

   $Request= $Pdo_Object->prepare($Sql_Query);

   $Request->execute($Arr_Key_Value);

   $Datatable = $Request->fetchAll();
}
catch(PDOException $pdo_e){
  //Faire quelque choses en cas d'erreur PDO
}
catch(Exception $e){
  //Pour les autres erreurs faire autre chose 
}

Conclusion

Une bonne intégration dans l'usage de l'objet PDO grâce une utilisation adaptée des différents types de requête permet de garantir une inviolabilité de votre base de données via les failles SQL.