Prévention des injections SQL en PHP (et d’autres vulnérabilités)
Si vous êtes dans le développement Web depuis un certain temps, vous avez presque certainement entendu le terme « injection SQL » et des histoires terrifiantes à ce sujet.
PHP, comme beaucoup d’autres langages, n’est pas à l’abri de ce type de menace, qui peut être très dangereuse en effet. Mais, heureusement, la protection de vos sites Web contre l’injection SQL et d’autres menaces similaires est quelque chose vers lequel vous pouvez prendre des mesures tangibles.
Dans cet article, vous apprendrez ce qu’est l’injection SQL, quelles sont les conséquences d’une attaque réussie, comment un attaquant peut tirer parti du code PHP vulnérable, ce que vous pouvez faire pour l’empêcher et quels outils vous pouvez utiliser pour détecter les parties de votre code qui pourraient être soumises à ce type de menace. De plus, vous découvrirez quelques autres vulnérabilités courantes dont vous devez être conscient afin de créer des applications plus sécurisées.
Comment fonctionne une injection SQL ?
La première chose que vous devez savoir pour protéger votre code de l’injection SQL est de comprendre comment il pourrait être exploité par un attaquant.
L’idée derrière l’exploit est plutôt simple: un attaquant exécute du code SQL malveillant sur votre base de données via votre application.
Comment quelqu’un pourrait-il y parvenir? En abusant des entrées de votre application.
Regardons un exemple simple. Supposons que vous ayez une application affichant une liste de noms d’utilisateur, où chaque utilisateur a un lien vers ses détails comme ceci:
Puis, sur le fichier user_details.php
, vous avez ceci:
<?php $sql = "SELECT * FROM user WHERE id = ".$_GET;
Si l’utilisateur agit comme prévu et clique sur le nom d’un utilisateur, l’URL de la demande ressemblera à ceci : http://domain.com/user_details.php?id=8
.
Alors, qu’est-ce qui ne va pas?
La valeur de $sql
sera la chaîne "SELECT * FROM user WHERE id = 8"
et la requête s’exécutera très bien.
Le problème se trouve dans la partie en italique de cette phrase: Si l’utilisateur agit comme prévu et clique sur le nom d’un utilisateur, l’URL de la demande ressemblera à ceci:
http://domain.com/user_details.php?id=8
.
Que se passe-t-il si l’utilisateur n’agit pas comme prévu ?
Et si quelqu’un fabriquait une URL envoyant autre chose qu’un numéro comme identifiant ?
URL fabriquées
Si l’URL était quelque chose comme http://domain.com/user_details.php?id=Peanuts
, la chaîne SQL résultante serait : "SELECT * FROM user WHERE id = Peanuts"
.
Dans ce cas, le problème serait pour l’utilisateur. La requête échouerait simplement et, dans le pire des cas, l’utilisateur verrait une sorte de message étrange.
Mais que se passe-t-il si l’URL ressemblait plus à ceci:
http://domain.com/user_details.php?id=1+OR+1%3B
? (C’est le résultat de urlencode('1 OR 1;')
, d’ailleurs.)
Dans ce cas, le SQL résultant serait "SELECT * FROM user WHERE id = 1 OR 1;"
. Cela signifie que le résultat de la requête serait chaque utilisateur de la base de données au lieu de simplement l’utilisateur dont l’ID est égal à 1. Ouch.
Mais ce n’est toujours pas trop mal, non?
Essayons maintenant un exemple plus extrême.
Supposons que l’URL soit quelque chose comme
http://domain.com/user_details.php?id=1%27%3B+DROP+TABLE+users%3B--+
, ce qui se traduit par id=1; DROP TABLE users;--
, produisant efficacement cette commande SQL:
"SELECT * FROM users WHERE id = 1; DROP TABLE users;-- "
. Et si vous exécutez cette requêtebig grand aïe.
Si vous n’êtes pas familier à 100% avec la syntaxe SQL, DROP TABLE
supprimerait complètement la table. Ce serait comme si ça n’avait jamais existé.
Bien sûr, une sauvegarde récente aiderait à réduire les dégâts, mais quand même. C’est mauvais.
Si vous voulez en savoir plus sur ce type d’attaque, il y a un excellent article ici.
Mais je pense que c’est assez de mauvaises nouvelles. Passons à un endroit plus joyeux: comment empêcher ces types d’injections SQL dans vos applications PHP.
Comment empêcher les injections SQL en PHP
Comme vous l’avez vu, le problème est lorsqu’un attaquant a la capacité d’exécuter du code SQL sans que vous le sachiez.
Cette situation se présente comme une conséquence de la création de requêtes à la volée via la concaténation de chaînes et de l’inclusion de données collectées à partir d’entrées externes (généralement des entrées utilisateur, mais également d’autres sources externes telles que des fichiers, des réponses aux appels d’API, etc.).
Donc, afin d’éviter cette vulnérabilité dans votre code, vous devez résoudre les sources de problèmes potentiels.
Validez vos entrées
Un moyen simple de corriger la vulnérabilité dans l’exemple ci-dessus serait de vérifier si le paramètre ID est ce qui est réellement attendu de lui (c’est-à-dire, un entier positif):
Une autre façon de faire ce type de validation est d’exploiter les filtres intégrés de PHP:
<?php
$id = filter_input( INPUT_GET, 'id', FILTER_VALIDATE_INT);
Ainsi, en validant vos entrées, vous empêchez les attaquants d’exécuter du code malveillant aux côtés du vôtre.
Il n’y a pas de moyen facile de les empêcher d’essayer, mais tant que leurs tentatives ne réussissent pas, vous êtes prêt à partir.
Utilisez des instructions préparées
Une autre façon de protéger votre code contre les injections SQL consiste à utiliser des instructions préparées. Les instructions préparées sont des commandes SQL précompilées.
Ils peuvent être utilisés avec une bibliothèque d’accès à la base de données spécifique (telle que mysqli) ou avec la bibliothèque plus générique PDO.
Jetons un coup d’œil à un exemple utilisant mysqli:
Si vous essayez ce code (en remplaçant les informations d’identification de la base de données, bien sûr), vous verrez que, si vous y accédez via une URL légale comme http://domain.com/user_details.php?id=1
, vous obtiendrez le même résultat que l’une des URL malveillantes ci-dessus (qui sont les informations sur l’utilisateur associé à l’ID 1 et rien d’autre).
Si vous préférez utiliser PDO, le code ressemblera davantage à ceci:
Pas vraiment de différence, non?
En résumé, les instructions préparées offrent un excellent moyen d’empêcher les injections SQL. En outre, ils fonctionnent généralement mieux que le SQL à la volée.
Maintenant que vous connaissez l’outil, il vous suffit de l’utiliser.
Comment détecter le code PHP vulnérable aux injections SQL
Une fois la vulnérabilité trouvée, la réparer n’est pas vraiment compliqué. Mais comment trouvez-vous ces types de vulnérabilités dans votre code en premier lieu?
Eh bien, si vous avez de la chance et que votre base de code est assez petite, un simple examen du code fera l’affaire.
Mais si votre application est de taille moyenne à grande, vous aurez besoin d’aide supplémentaire.
Un outil très populaire (et open-source) que vous pouvez utiliser à cette fin est sqlmap, un script Python simple qui tentera d’attaquer n’importe quelle URL que vous lui transmettez et de rendre compte de ses résultats.
Voici un exemple de sortie d’une exécution sur un de mes sites Web basés sur PHP:
Un autre outil open source que vous pouvez utiliser est Arachni, un outil plus sophistiqué qui comprend une interface graphique Web et a été écrit en Ruby.
Voici un exemple de sortie de son exécution via la CLI:
Au-delà de la révision et des tests de code, vous devriez envisager d’utiliser une solution RASP, telle que Sqreen, pour surveiller vos environnements de production afin d’arrêter l’exécution de tous les exploits qui passent les étapes de révision et de test.
Quelles autres vulnérabilités devez-vous connaître ?
Injections de code
L’injection SQL n’est qu’un membre d’une plus grande famille de vulnérabilités : l’injection de code.
L’idée derrière les différentes techniques d’injection de code est toujours la même. Il s’agit de tromper une application innocente en exécutant du code malveillant.
Cela peut être fait de plusieurs manières.
En PHP, il existe des fonctions conçues pour émettre simplement des commandes directement dans le système d’exploitation, telles que la fonction exec
.
Voici un exemple simple de la façon dont cela pourrait être exploité:
<?php
exec( 'cp uploads/'.$_GET.' /secure/');
Si quelqu’un demandait http://domain.com/exec.php?file=a.txt%3B+rm+-Rf+%2A+%23
, le résultat serait l’exécution de cette commande:
cp uploads/a.txt; rm -Rf * # /secure/
Et cela pourrait effacer tout le disque sur le serveur — l’entrée non validée frappe à nouveau!
Script intersite
Une autre vulnérabilité très courante est le script intersite (XSS). Dans ce cas, la victime est l’utilisateur visitant votre site.
XSS est lorsqu’un attaquant injecte du code JavaScript malveillant dans un formulaire HTML normal, qui sera ensuite rendu par le navigateur d’un autre utilisateur.
Regardez ce script PHP:
<?php
echo "<html><p>Hello {$_GET}!</p></html>";
Clairement, l’intention ici est de créer une page simple qui accueille l’utilisateur.
Mais quand les méchants y arrivent, qu’est-ce qui les empêche de demander
http://domain.com/greet.php?name=Mauro%3Cscript+language%3D%22javascript%22%3Ealert%28%22You+are+hacked%21%22%29%3B%3C%2Fscript%3E
? Cette requête produira le code HTML suivant en sortie:
<html><p>Hello Mauro<script language="javascript">alert("You are hacked!");</script></p></html>
C’est clairement un exploit très naïf — après tout, quel est le mal de montrer à quelqu’un un simple « Vous êtes piraté! » message ? Mais les choses peuvent devenir assez sérieuses avec un peu d’imagination. Gardez simplement à l’esprit que chaque demande adressée à un serveur inclut le cookie de session, de sorte que cette vulnérabilité peut être à l’origine du détournement des sessions de vos utilisateurs.
Là encore, alors que les problèmes semblent moches, les correctifs sont vraiment simples: tout est une question de validation et de désinfection.
Dans le cas de XSS, un simple appel à htmlentities
avant de produire la sortie réelle fera l’affaire.
Corriger par rapport à détecter les vulnérabilités de sécurité
Comme vous l’avez vu dans cet article, corriger les vulnérabilités de sécurité — à la fois les injections SQL et d’autres types — n’est pas le principal défi. Réaliser que votre code est vulnérable, et où il peut être exploité, l’est. Vous avez quelques options ici:
- Ayez une très forte discipline de codage
- Testez soigneusement vos applications pour détecter les problèmes de sécurité
- Tirez parti des outils de surveillance et de protection pour prévenir les exploits et recevoir des alertes en temps opportun
Lequel vous choisissez dépend de vous. L’important est de prêter attention à ces problèmes et d’agir sur ces connaissances. Restez en sécurité.