Rappel sur le concept d'injection SQL
L'idée est 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.
L'injection
SQL,
est 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).
La technique d'injection SQL la plus courante
vise à forcer l'analyseur de requête SQL à ignorer le reste de la
requête en utilisant les symboles de mise en
commentaires.
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, …).
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ésente de la manière suivante
:
$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 concaténation du PHP génèrera une requête SQL qui ne
tiendra pas compte du mot de passe.
On parle de perméabilité aux injections SQL.
Concept de protection contre les injections
SQL
La
sécurisation doit s'effectuer sur plusieurs niveaux pour limiter la
surface d'attaque.
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). Seules les données "sécurisées" devraient être
utilisées dans la suite du flux d'exé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 œuvre
protège efficacement les applications contre les risques
d'injection SQL.
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
Avec Wamp :


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
$Pdo_Object=
newPDO("mysql:host=127.0.0.1;dbname=DataBase","user","password",array(PDO::ATTR_ERRMODE=>
PDO::ERRMODE_EXCEPTION));
Inutile d'ouvrir la connexion à la base de données MySQL avec PDO
en PHP
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.
$Pdo_Object=
null;
Les requêtes MySQL en PHP avec PDO
Pour exécuter une requête "classique" en MySQL, on
déclare la 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.
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" pour se protéger
des injections
SQL avec
PHP et PDO
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.
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é des
requêtes préparées 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. 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 (cela fonctionne
aussi avec des requêtes comme : insert, update, delete,
…)
try
{
$isbn=
Clear_Front_Variable($_GET['isbn']);
$allow=
Valid_Isbn_Format($isbn);
if(!$allow)throw
newException("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
}
Les fonctions Clear_Front_Variable et Valid_Isbn_Format ne
servent qu'à titre d'exemple. Ce sont des fonctions PHP qui
contiennent respectivement des utilisations des fonctions
natives preg_replace (pour
supprimer les caractères attendus) et à preg_match (pour
contrôler que le format final après remplacement, correspond
strictement au format spécifique attendu). On peut également
utiliser les expressions régulières (vues plus tard).
Conclusion
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.