Vous pensez donc que votre base de données SQL est performante et à l'abri de la destruction instantanée? Eh bien, SQL Injection n'est pas d'accord!
Oui, nous parlons de destruction instantanée, car je ne veux pas ouvrir cet article avec la terminologie boiteuse habituelle de «renforcer la sécurité» et «empêcher l'accès malveillant». Injection SQL est une telle vieille astuce dans le livre que tout le monde, chaque développeur, le sait très bien et sait bien comment l'empêcher. Sauf pour cette fois étrange où ils dérapent, et les résultats peuvent être tout simplement désastreux.
Si vous savez déjà ce qu'est l'injection SQL, n'hésitez pas à passer à la seconde moitié de l'article. Mais pour ceux qui viennent juste de se lancer dans le domaine du développement Web et qui rêvent d'assumer des rôles plus importants, une introduction s'impose.
What is SQL Injection?
La clé pour comprendre SQL Injection est dans son nom: SQL + Injection. Le mot «injection» ici n'a aucune connotation médicale, mais plutôt l'utilisation du verbe «injecter». Ensemble, ces deux mots véhiculent l'idée de mettre SQL dans une application Web.
Mettre SQL dans une application Web. . . hmmm. . . N'est-ce pas ce que nous faisons de toute façon? Oui, mais nous ne voulons pas qu'un attaquant pilote notre base de données. Comprenons cela à l'aide d'un exemple.
Disons que vous créez un site Web PHP typique pour un magasin de commerce électronique local, vous décidez donc d'ajouter un formulaire de contact comme celui-ci:
<form action="record_message.php" method="POST">
<label>Your name</label>
<input type="text" name="name">
<label>Your message</label>
<textarea name="message" rows="5"></textarea>
<input type="submit" value="Send">
</form>
Et supposons que le fichier send_message.php
stocke tout dans une base de données afin que les propriétaires de magasin puissent lire les messages des utilisateurs ultérieurement. Il peut avoir un code comme celui-ci:
<?php
$name = $_POST['name'];
$message = $_POST['message'];
// check if this user already has a message
mysqli_query($conn, "SELECT * from messages where name = $name");
// Other code here
Vous essayez donc d'abord de voir si cet utilisateur a déjà un message non lu. La requête SELECT * from messages where name = $name
semble assez simple, non?
FAUX!
Dans notre innocence, nous avons ouvert les portes à la destruction instantanée de notre base de données. Pour que cela se produise, l'attaquant doit remplir les conditions suivantes:
- L'application s'exécute sur une base de données SQL (aujourd'hui, presque toutes les applications sont)
- La connexion actuelle à la base de données dispose des autorisations «modifier» et «supprimer» sur la base de données
- Les noms des tables importantes peuvent être devinés
Le troisième point signifie que maintenant que l'attaquant sait que vous exploitez une boutique de commerce électronique, vous stockez très probablement les données de commande dans un orders
table. Armé de tout cela, tout ce que l'attaquant doit faire est de fournir ceci comme son nom:
Joe; truncate orders;
? Oui monsieur! Voyons ce que deviendra la requête lorsqu'elle sera exécutée par le script PHP:
SELECT * FROM messages WHERE name = Joe; truncate orders;
D'accord, la première partie de la requête a une erreur de syntaxe (pas de guillemets autour de «Joe»), mais le point-virgule force le moteur MySQL à commencer à en interpréter un nouveau: truncate orders
. Juste comme ça, en un seul coup, tout l'historique des commandes a disparu!
Maintenant que vous savez comment fonctionne l'injection SQL, il est temps de voir comment l'arrêter. Les deux conditions qui doivent être remplies pour une injection SQL réussie sont:
- Le script PHP doit avoir des privilèges de modification / suppression sur la base de données. Je pense que cela est vrai pour toutes les applications et que vous ne pourrez pas rendre vos applications en lecture seule. 🙂 Et devinez quoi, même si nous supprimons tous les privilèges de modification, l'injection SQL peut toujours permettre à quelqu'un d'exécuter des requêtes SELECT et d'afficher toute la base de données, données sensibles incluses. En d'autres termes, la réduction du niveau d'accès à la base de données ne fonctionne pas et votre application en a de toute façon besoin.
- L'entrée utilisateur est en cours de traitement. La seule façon dont l'injection SQL peut fonctionner est d'accepter les données des utilisateurs. Encore une fois, il n'est pas pratique d'arrêter toutes les entrées de votre application simplement parce que vous vous inquiétez de l'injection SQL.
Preventing SQL injection in PHP
Maintenant, étant donné que les connexions à la base de données, les requêtes et les entrées utilisateur font partie de la vie, comment pouvons-nous empêcher l'injection SQL? Heureusement, c'est assez simple, et il y a deux façons de le faire: 1) nettoyer les entrées de l'utilisateur et 2) utiliser des instructions préparées.
Nettoyer les entrées utilisateur
Si vous utilisez une ancienne version de PHP (5.5 ou inférieure, et cela arrive souvent sur l'hébergement mutualisé), il est sage d'exécuter toutes vos entrées utilisateur via une fonction appelée mysql_real_escape_string()
. Fondamentalement, ce qu'il fait supprime tous les caractères spéciaux d'une chaîne afin qu'ils perdent leur signification lorsqu'ils sont utilisés par la base de données.
Par exemple, si vous avez une chaîne comme I'm a string
, le caractère guillemet simple (') peut être utilisé par un attaquant pour manipuler la requête de base de données en cours de création et provoquer une injection SQL. Le courir à travers mysql_real_escape_string()
produit I\'m a string
, qui ajoute une barre oblique inverse au guillemet simple, en l'échappant. En conséquence, la chaîne entière est maintenant transmise sous forme de chaîne inoffensive à la base de données, au lieu de pouvoir participer à la manipulation de la requête.
Il y a un inconvénient à cette approche: c'est une technique vraiment très ancienne qui va de pair avec les anciennes formes d'accès aux bases de données en PHP. Depuis PHP 7, cette fonction n'existe même plus, ce qui nous amène à notre prochaine solution.
Utilisez des déclarations préparées
Les instructions préparées sont un moyen de rendre les requêtes de base de données plus sûres et plus fiables. L'idée est qu'au lieu d'envoyer la requête brute à la base de données, nous indiquons d'abord à la base de données la structure de la requête que nous enverrons. C'est ce que nous entendons par «préparer» une déclaration. Une fois qu'une déclaration est préparée, nous transmettons les informations en tant qu'entrées paramétrées afin que la base de données puisse «combler les lacunes» en branchant les entrées dans la structure de requête que nous avons envoyée auparavant. Cela enlève toute puissance spéciale que les entrées pourraient avoir, les obligeant à être traitées comme de simples variables (ou des charges utiles, si vous voulez) dans tout le processus. Voici à quoi ressemblent les déclarations préparées:
<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDB";
// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
// prepare and bind
$stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)");
$stmt->bind_param("sss", $firstname, $lastname, $email);
// set parameters and execute
$firstname = "John";
$lastname = "Doe";
$email = "john@example.com";
$stmt->execute();
$firstname = "Mary";
$lastname = "Moe";
$email = "mary@example.com";
$stmt->execute();
$firstname = "Julie";
$lastname = "Dooley";
$email = "julie@example.com";
$stmt->execute();
echo "New records created successfully";
$stmt->close();
$conn->close();
?>
Je sais que le processus semble inutilement complexe si vous êtes nouveau dans les déclarations préparées, mais le concept en vaut la peine. Voici une belle introduction.
Pour ceux qui sont déjà familiers avec l'extension PDO de PHP et qui l'utilisent pour créer des déclarations préparées, j'ai un petit conseil.
Attention: soyez prudent lors de la configuration de PDO
Lorsque vous utilisez PDO pour accéder à la base de données, nous pouvons être aspirés dans un faux sentiment de sécurité. «Ah, eh bien, j'utilise PDO. Maintenant, je n'ai plus besoin de penser à autre chose »- c'est ainsi que se déroule généralement notre réflexion. Il est vrai que PDO (ou instructions préparées par MySQLi) suffit à empêcher toutes sortes d'attaques par injection SQL, mais il faut être prudent lors de sa configuration. Il est courant de simplement copier-coller du code à partir de didacticiels ou de vos projets précédents et de passer à autre chose, mais ce paramètre peut tout annuler:
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
Ce paramètre indique à PDO d'émuler les instructions préparées plutôt que d'utiliser réellement la fonction d'instructions préparées de la base de données. Par conséquent, PHP envoie des chaînes de requête simples à la base de données même si votre code semble créer des instructions préparées et définir des paramètres et tout cela. En d'autres termes, vous êtes aussi vulnérable à l'injection SQL qu'avant. 🙂
La solution est simple: assurez-vous que cette émulation est définie sur false.
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
Maintenant, le script PHP est obligé d'utiliser des instructions préparées au niveau de la base de données, empêchant toutes sortes d'injections SQL.
Preventing using WAF
Savez-vous que vous pouvez également protéger les applications Web de l'injection SQL en utilisant WAF (pare-feu d'application Web)?
Eh bien, pas seulement l'injection SQL, mais bien d'autres vulnérabilités de la couche 7 telles que les scripts intersites, l'authentification interrompue, la falsification intersites, l'exposition de données, etc. Soit vous pouvez utiliser un hébergement auto-hébergé comme Sécurité Mod ou basé sur le cloud comme suit.
SQL injection and modern PHP frameworks
L'injection SQL est si courante, si facile, si frustrante et si dangereuse que tous les Framework Web PHP sont intégrés avec des contre-mesures. Dans WordPress, par exemple, nous avons le $wpdb->prepare()
fonction, alors que si vous utilisez un framework MVC, il fait tout le sale boulot pour vous et vous n'avez même pas à penser à empêcher l'injection SQL. C'est un peu ennuyeux que dans WordPress vous deviez préparer des déclarations explicitement, mais bon, c'est de WordPress dont nous parlons. 🙂
Quoi qu'il en soit, ce que je veux dire, c'est que la race moderne de développeurs Web n'a pas à penser à l'injection SQL et, par conséquent, ils ne sont même pas conscients de cette possibilité. Ainsi, même s'ils en laissent un détourné ouvert dans leur application (peut-être s'agit-il d'un paramètre de requête $_GET et de vieilles habitudes de déclenchement d'une requête sale), les résultats peuvent être catastrophiques. Il est donc toujours préférable de prendre le temps de plonger plus profondément dans les fondations.
Conclusion
L'injection SQL est une attaque très désagréable sur une application Web mais est facilement évitée. Comme nous l'avons vu dans cet article, être prudent lors du traitement des entrées utilisateur (d'ailleurs, l'injection SQL n'est pas la seule menace que la gestion des entrées utilisateur apporte) et interroger la base de données est tout ce qu'il y a à faire. Cela dit, nous ne travaillons pas toujours dans la sécurité d'un framework web, il vaut donc mieux être conscient de ce type d'attaque et ne pas y tomber.