Introduction au concept d'injection SQL
L'idée de cet article est de permettre aux plus néophytes d'entre nous de parvenir à créer un script PHP (avec l'objet PDO pour interroger une base de données MySQL) qui ne sera pas perméable aux techniques permettant de réaliser des attaques par injection SQL. Ne sera pas détaillée la mise en oeuvre d'injection SQL au-delà du strict nécessaire à la compréhension.
Les développeurs débutants sont bien souvent peu ou pas conscients des possibilités énormes de manipulation possibles avec les requêtes SQL, donc des risques d'injection SQL. Ils supposent à tort que les requêtes SQL sont des instructions fiables. Or il n'en est rien. Une requête SQL est par nature, en mesure de contourner tout ou partie des contrôles de sécurités d'une application.
L'injection SQL, est donc possible uniquement quand une requête est générée à partir de données fournies par un utilisateur. Données utilisées directement pour construire tout ou partie d'une requête SQL (insert, update, jointure, conditions de filtrages, de regroupement).
Les injections SQL (SQLi)
L'attaque par injection SQL (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 requête SQL dans son contexte applicatif. 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 SQL 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. En PHP une requête SQL avec cette faille se présenterait de la manière suivante :
Si un utilisateur mal intentionné assigne à la valeur $_POST['login']
la valeur : "admin';--"
alors la concaténation du PHP génèrera une requête SQL qui ne tiendra pas comptes du mot de passe.
On parle de pérméabilité aux injections SQL.
Concept de protection contre les injections SQL
Premier niveau de lutte contre les injections SQL, les droits SQL de l'utilisateur
Dans un schéma idéal de sécurisation, il devrait systématiquement y avoir deux connexions SQL, une, utilisée pour accéder aux ressources en lecture, et une seconde en écriture. Chacune avec le minimum de droit requis pour les besoins de l'application. Ce modèle de sécurisation permet de limiter la portée de chaque injection SQL possible. Cette stratégie n'est pas toujours possible (limite de l'hébergement), et à défaut, le compte utilisateur utilisé devrait avoir les accès minimums nécessaires au fonctionnement de l'application.
Second niveau de protection contre les injections SQL, la couche d'accès aux données
Dans une architecture MVC ou trois tiers, la DAL (data access layer) est l'objet qui permet de s'interfacer avec la base de données pour exécuter les requêtes SQL (exemple : l'objet PDO en PHP). Elle devrait systématiquement être intégrée dans les applicatifs via un wrapper (surcouche logicielle) qui réaliserait des préparations des requêtes avant leur exécution de manière à garantir que 100% des requêtes sont utilisées via une requête préparée.
Troisième niveau de défense contre les injections SQL, le modèle / l'objet relationnel
Toujours dans le cadre d'une architecture professionnelle, les données émanant de la base de données ou d'un formulaire, devraient passer par un objet relationnel qui réaliserait les contrôles et les transcodages nécessaires, sur le format des données attendues (ou à défaut retourner une valeur prédéfinie, voir annuler le traitement) de manière à garantir le typage et le format des données.
Quatrième et dernière ligne de défense contre les injections SQL, les données du front
Les données qui proviennent du front que ce soit un formulaire utilisateur ou une API exposée doivent être immédiatement nettoyées puis contrôlées, pour répondre au format attendu par rapport à leur nom de paramètre. (ex : un paramètre « id » attend exclusivement un entier supérieur à zéro, mais rien d'autre). Seule les données "sécurisées" devraient êtres utilisées dans la suite du flux d'éxécution de l'application.
L'objet PHP Data Object (PDO)
En PHP, l'objet PDO est une classe qui permet de s'interfacer avec une base de données ici en MySQL pour communiquer avec elle via des requêtes SQL. Une bonne pratique dans sa mise en oeuvre protégera efficacement vos applications contre les risques d'injection SQL.
- Définition du pilote de SGBD pour utiliser PHP PDO en MySQL
- Constructeur de l'objet PDO en PHP et chaîne de connexion pour MySQL
- Gestion de la connexion à la base de données MySQL avec PDO en PHP
- Les requêtes MySQL en PHP avec PDO
- PHP PDO et les requêtes préparées
Définition du pilote de SGBD pour utiliser PHP PDO en MySQL
Pour définir le pilote à utiliser (MySql,PostGr,etc) par le serveur qui héberge votre instance de PHP 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 en PHP et chaîne de connexion pour MySQL
Pour initialiser notre objet PDO dans le code PHP (à noter que la chaine de connexion dépend du moteur de sgbd utilisé).
On lui soumet les différentes informations :
- host : l'adresse ip ou l'adresse dns du serveur cible hébergeant la base de donnée MySQL
- dbname : le nom de la base que l'on souhaite interroger.
- user : à remplacer par votre profil utilisateur
- password : à remplacer par le mot de passe du profil utilisateur utilisé
- PDO::ATTR_ERRMODE : demande une erreur typée PDOException si une erreur survient
L'objet PDO de PHP ne nécessite pas d'ouvrir explicitement la connexion à la base de données MySQL avant de pouvoir communiquer avec elle. Cette connexion étant nativement établie à partir de l'initialisation de l'objet PDO de PHP.
Fermer la connexion à la base MySQL en PHP avec PDO
Pour fermer la connexion à la base de données MySQL il suffit de rendre null l'objet PDO précédemment créer dans notre script PHP.
Pour exécuter une requête "classique" en MySQL, on déclare notre requête dans le PHP avec 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 ainsi retournées.
En PHP , les requêtes préparées de l'objet PDO offrent une couche de sécurité supplémentaire à vos requêtes MySQL car elles effectuent les contrôles nécessaires à la bonne sécurisation des données envoyées en paramètres.
Vu qu'aucune information n'est précisée et bien que la sécurité des requêtes préparés de l'objet PDO n'est plus à démontrer, on est sur une "boîte noire" il convient donc d'appliquer les contrôles de bases en amont. Dans notre script PHP 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 nettoyée correspond bien au format attendu. Si le format est discordant, on lève une erreur.
Maintenant pour les requêtes préparées le processus est légèrement différent.
- On commence par créer un tableau associatif qui contient des couples clés=>valeur.
- Puis on écrit notre requête MySQL. En assignant les "clés" aux emplacements que nous souhaitons, en prenant le soin des pré-fixers de deux points (ex : :target_isbn).
- Encore une fois on soumet la requête à l'objet PDO mais par la fonction PHP prepare().
- Puis via l'appel de la fonction de l'objet PDO PHP execute() on déclenche la sélection (nb : marche aussi avec des requêtes d'altérations : insert, update, delete, etc.)
En PHP, une bonne intégration dans l'usage de l'objet PDO est indispensable pour se prémunir des risques d'injection SQL. Le PHP offre de nombreux outils permettant de contrôler et de garantir le format des données (principalement : preg_match
et preg_replace
). Par ailleurs grâce à une utilisation adaptée de l'objet PDO pour préparer les requêtes MySQL il devient impossible pour un attaquant de parvenir à ses fins avec une injection SQL.