Injection SQL
De nombreux développeurs web ne sont pas conscients des possibilités de manipulation des requêtes SQL, et supposent que les requêtes SQL sont des commandes sûres. Cela signifie qu'une requête SQL est capable de contourner les contrôles et vérifications, comme les identifications, et parfois, les requêtes SQL ont accès aux commandes d'administration.
L'injection SQL directe est une technique où un pirate modifie une requête SQL existante pour afficher des données cachées, ou pour écraser des valeurs importantes, ou encore exécuter des commandes dangereuses pour la base. Cela se fait lorsque l'application prend les données envoyées par l'internaute, et l'utilise directement pour construire une requête SQL. Les exemples ci-dessous sont basés sur une histoire vraie, malheureusement.
Avec le manque de vérification des données de l'internaute et la connexion au serveur avec des droits de super utilisateur, le pirate peut créer des utilisateurs, et créer un autre super utilisateur.
Exemple #1 Séparation des résultats en pages, et créer des administrateurs (PostgreSQL et MySQL)
<?php $offset = $argv[0]; // Attention, aucune validation! $query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; $result = pg_query($conn, $query); ?>
Exemple #2 Exemple d'injection SQL
<?php $query = "SELECT id, name, inserted, size FROM products WHERE size = '$size'"; $result = odbc_exec($conn, $query); ?>
Exemple #4 Révélation des mots de passe
<?php $query= "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';"; ?>
Exemple #6 Une requête et son injection
<?php // $uid == ' or uid like '%admin% $query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';"; // $pwd: hehehe', trusted=100, admin='yes $query = "UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' WHERE ...;"; ?>
C'est un exemple terrible d'acquisition de droits d'administrateur sur un serveur de base de données.
Exemple #7 Attaque d'un serveur de bases de données (MSSQL Server)
<?php $query = "SELECT * FROM products WHERE id LIKE '%$prod%'"; $result = mssql_query($query); ?>
Exemple #8 Attaque d'un serveur de base de données (MSSQL Server) - 2
<?php $query = "SELECT * FROM products WHERE id LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD' --%'"; $result = mssql_query($query); ?>
Note:
Certains des exemples ci-dessus sont spécifiques à certains serveurs de bases de données. Cela n'empêche pas des attaques similaires d'être possibles sur d'autres produits. Votre base de données sera alors vulnérable d'une autre manière.
Techniques de contournement
Bien qu'il semble évident qu'un pirate doit posséder quelques connaissances de l'architecture de la base de données afin de conduire avec succès une attaque, il est souvent très simple de les obtenir. Par exemple, si la base de données fait partie d'un paquet open source ou disponible publiquement, ces informations sont complètement ouvertes et disponibles. Ces informations peuvent aussi être divulgués pour des codes sources fermés - y compris si ce code est encodé, occulté, ou compilé - aux travers des messages d'erreurs. D'autres méthodes consistent à deviner l'utilisateur de table commune ainsi que des noms des colonnes. Par exemple, un formulaire d'identification qui utilise la table 'users' avec les colonnes de noms 'id', 'username', et 'password'.
Ces attaques sont généralement basées sur l'exploitation de code qui n'est pas écrit de manière sécuritaire. N'ayez aucune confiance dans les données qui proviennent de l'utilisateur, même si cela provient d'un menu déroulant, d'un champ caché ou d'un cookie. Le premier exemple montre comment une requête peut causer un désastre.
- Ne nous connectez jamais sur une base de données en tant que super utilisateur ou propriétaire de la base. Utilisez toujours un utilisateur adapté, avec des droits très limités.
- Utilisez des requêtes préparées avec des variables liées. Elles sont disponibles avec PDO, MySQLi ainsi que d'autres bibliotèques.
- Vérifiez que les données ont bien le type attendu. PHP dispose d'un éventail de fonction de validation large, depuis les plus simples, de la section Variables et la section Caractères (e.g. is_numeric(), ctype_digit() respectivement) aux fonctions avancées de Expression rationnelle Perl.
-
Si l'application attend une entrée numérique, vérifiez vos données avec la fonction ctype_digit(), ou bien modifiez automatiquement le type avec la fonction settype(), ou encore avec sprintf().
Exemple #9 Une navigation de fiches plus sécuritaire
<?php settype($offset, 'integer'); $query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; // notez que %d dans la chaîne de format : %s serait inutile $query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;", $offset); ?>
- Si la couche de base de données ne suppose pas les variables liées, alors, mettez entre guillemets toutes les valeurs non numériques qui sont passées à la base de données avec la fonction spécifique à la base de données d'échappement de caractères (e.g. mysql_real_escape_string(), sqlite_escape_string(), etc.). Les fonctions génériques comme addslashes() sont utiles uniquement dans un environnement très spécifique (i.e. MySQL avec un jeu de caractères sur un seul octet avec NO_BACKSLASH_ESCAPES désactivé), aussi, il est préférable de ne pas les utiliser.
- N'affichez jamais d'informations spécifiques à la base, et notamment des informations concernant le schéma. Voyez aussi la section Rapport d'erreur et le chapitre Gestion des erreurs.
- Vous pouvez avoir des procédures stockées et des curseurs prédéfinis qui font que les utilisateurs n'ont pas un accès direct aux tables ou vues, mais cette solution a d'autres impacts.
À côté de ces conseils, il est recommandé d'enregistrer vos requêtes, soit dans vos scripts, soit dans la base elle-même, si elle le supporte. Évidemment, cet enregistrement ne sera pas capable d'empêcher une attaque, mais vous permettra de retrouver la requête qui a fauté. L'historique n'est pas très utile par lui-même, mais au niveau des informations qu'il contient. Plus vous avez de détails, mieux c'est.