L’altération SQL
(souvent restreinte à l’injection SQL) est une technique permettant de récupérer des informations confidentielles de la base de données, ou simplement des méta données, en outrepassant les droits applicatifs ‘normaux’. Elle peut également être utilisée pour modifier ou détruire des données.
Le principe est de modifier indirectement les ordres SQL envoyés au serveur, en y incluant des chaînes de caractères spéciales en lieu et place des paramètres attendus par l’applicatif.
L’essentiel crédit de cette technique revient à ‘Rain Forest Puppy’ http://www.wiretrip.net/rfp/
Le passage de paramètres
L’altération SQL est facilement réalisable au sein d’une application web dynamique en enrichissant les données d’un champ de formulaire ou en étendant / modifiant artificiellement les paramètres de l’URL d’appel d’un programme serveur
<Form method=”GET” action=”traitement.php” >
<input type=”text” name=”nom”>
<input type=”submit” value=”Rechercher”>
</form>
remarque les paramètres sont visibles dans l’URL lorsque l’on utilise une méthode GET dans un formulaire) http://serveur/prog?par1=val1& par2=val2&par3…mais pas lorsqu’on utiliser une méthode ‘POST’
on aurait ici pour appeler directement le programme PHP sans passer par le formulaire :
http://serveur.domaine/traitement.php?nom=DELEGLISE
dans le progarmme PHP qui traite la ou les données du formulaire on aura généralement des choses du style :
<?
$sql = “SELECT * FROM users WHERE username LIKE ‘%$nom%’”
?>
L’ordre SQL est ici dynamique, il a une partie variable et est finalisé avec les données du formulaire pour pouvoir être exécuté.
Récupération directe d’informations
L’appel normal vu dans la barre d’adresse du navigateur est donc : http://serveur/traitement.php?nom=ddeleglise
Un appel falsifié serait : http://serveur/prog?nom=un_autre_user
Soit on a de la chance soit on connait un peu l’environnement et on peut essayer ‘admin’, ‘manager’, ’system,’ ‘root’…)
soit on a une erreur MAIS CELA PEUT AUSSI DONNER DES INDICATIONS !
invalid SQL statement ‘SELECT col1
FROM un_autre_user WHERE no= 246′ ,
‘un_autre_user’ table doesn’t exist.
On apprend le nom de user est un paramètre identifiant une table et qu’il y a une table par user, contenant un no qui sert de filtre. Cette chaine est simplement intercalée entre la clause FROM et la condition du SELECT.
‘SELECT col1 FROM’ . $parametre_user . ‘WHERE no = ‘. $no
Certaines des techniques suivantes nécessitent soit une connaissance spécifique de (la façon de coder) de l’applicatif, que notre ami ‘rain forest puppy’ a acquise en lisant les sources des versions de démo des logiciels concernés, soit de ‘parier’ sur une technique de codage ‘classique’ : traitement en boucle des paramètres fournis par un formulaire par exemple
Ajout de SQL supplémentaire en fin de champ
Le principe est de surcharger la valeur d’une variable saisie afin de passer des ordres SQL statiques EN PLUS de l’ordre SQL construit dynamiquement suite à la saisie.
Surcharge d’une variable numérique
Soit un champ de formulaire ‘no’ :1
à la saisie de 1 l’ordre SQL suivant est construit par le programme :
SELECT col FROM la_table WHERE no =1
mais si le controle de type de donnée saisi est permissif et que l’on saisit la valeur + un séparateur d’ordre SQL + un ordre SQL, par exemple :
no : 1; DELETE FROM EMP
l’ordre devient
SELECT col FROM la_table WHERE no =1; delete from EMP
Surcharge d’une variable chaîne (avec utilisation de commentaires)
nom : Deleglise
A la saisie de “Deleglise” (sans cote ni guillemet bien sur) , le programme serveur construit l’ordre SQL :
SELECT col FROM la_table WHERE nom = ‘Deleglise’
(l’ajout de cote par le programme est nécessaire pour que cet SQL soit syntaxiquement correct, une constante chaîne en SQL étant entre simple cote ). Il concatène le début du SELECT, une cote, la valeur, une cote.
Si l’on saisit : Deleglise’;delete from une_autre_table#
le programme serveur construit l’ordre SQL :
SELECT col FROM la_table WHERE nom = ‘Deleglise’;delete from une_autre_table ‘
ce qui est + intéressant mais syntaxiquement incorrect. Il suffira de masquer la dernière cote par un caractère adéquat.
exemple MYSQL
on utilise ‘#’ qui sert simplement à masquer les caractères qui suivent.
SELECT col FROM la_table WHERE nom = ‘Deleglise’;delete from une_autre_table# ‘
exemple Oracle
on utilise leS caractères ‘–’ qui servent à introduire un commentaire :
SELECT col FROM la_table WHERE nom = ‘Deleglise’;delete from une_autre_table–’
Variante : injection (proprement dite) de SQL
Prenons l’ordre SQL suivant, de construction classique.
‘SELECT col1 FROM’ . $parametre_user . ‘WHERE no = ‘. $no
Rien n’empêche de l’enrichir en insérant du SQL au milieu du SQL, tout en restant toujours syntaxiquement correct. Si $parametre_user prend la valeur : ‘ USERS; SELECT * FROM users’
on obtient :
‘SELECT col1 FROM’ . ‘ USERS; SELECT * FROM users ‘. ‘WHERE no = ‘. $no
ce qui enchaine 3 ordres SQL correct dont un qui recupere pas mal d’infos…
on aurait pu tenter pire, du genre ‘DELETE FROM users’ ou ‘DROP TABLE ddeleglise’ , mais il y a fort a parier que les droits auraient été insuffisants. Il est fréquent que les privilèges soient de consulter uniquement, soit de mettre à jour (UPDATE, INSERT, DELETE) les droits de CREATE, DROP et ALTER sont réservés (on espère) aux administrateurs !
Conditions toujours vraies
Soit une requête de connexion sur un serveur web du genre :
$sql = SELECT no FROM tab_users WHERE user=$nom AND password=$pwd
Lorsque l’on donnera un user et un mot de passe valide, et qui existent dans la table des users, on récuperera un no, qui donnera certains accès, dans la suite du programme. Tout cela est assez classique.
Si on arrive à ‘injecter’ une condition toujours vraie, on obtiendra un numéro, sans s’authentifier…
On sait que l’opérateur ‘OU’ est un peu laxiste : dès qu’un membre du ‘OU’ est vraie la condition complète est vraie…il suffit donc de rajouter un OU d’une expression toujours vraie.
Il en existe des tonnes en SQL : 1=1, 2>1, 1<>2,’A'=’A', NULL IS NULL, …
Alors on y va :
on saisit : ‘OR ‘A’='A pour $nom
comme $nom est encadré par des cotes par le programme, la première cote va se transformer en 2 cotes soit une chaine vide, et la deuxieme cote ajoutée fermera la cote de ‘A pour en faire une chaine !
$sql = SELECT no FROM tab_users WHERE user=$nom AND password=$pwd
devient
$sql = SELECT no FROM tab_users WHERE user=’ ‘OR ‘A’='A ‘ AND password=$pwd
– qui renverra TOUJOURS un no !
Utilisation de UNION
Cet opérateur ensembliste n’est disponible, que pour certaines versons de MySQL, mais existe pour toutes les versions d’Oracle.
On pourrait donc concaténer 2 SELECT par l’opérateur UNION. Le select d’origine
SELECT col FROM la_table WHERE nom = ‘Deleglise’
deviendrait par exemple :
SELECT col FROM la_table WHERE nom = ‘Deleglise’ UNION SELECT table_name FROM all_tables
et listerait en prime les noms des tables sur lesquelles j’ai des droits …
la seule contrainte (forte) de l’opérateur UNION est que les 2 SELECTs soient homogènes en terme de nombre de colonnes et de types, ce qui est le cas dans l’exemple (1 colonne de type caractere)
Mises à jour non prévue
L’hypothèse est ici que les champs de formulaire sont traités de manière générique, par une boucle itérative portant sur un tableau de variables de champs de formulaire. C’est la méthode la plus concise, et la plus universelle. PHP fournit par exemple des expressions qui implémentent ce genre de traitement ‘automatique’ en une seule ligne de code.
Prenons un formulaire simple à 2 champs : nom et prenom , et le code classique qui le gère :
while (champ_formulaire) {
$liste_maj = $nom_champ . ” = ‘ ” . $val_champ . “‘,” ;
}
$ordre_update = “UPDATE table_users SET ” . $liste_maj;
supprime_derniere_virgule($ordre_update);
sgbd.connexion.execute ($ordre_update);
La saisie de ‘Deleglise’ et ‘Didier’ donne :
update table_users set nom=’Deleglise’, prenom=’Didier’
La saisie de Deleglise pour nom et Didier \’ , niveau_privilege=\’admin pour prenom nous donne par contre :
update table_users set nom=’Deleglise’, prenom=’Didier’ , niveau_privilege=’admin’
;-( on met à jour 3 colonnes au lieu de 2, dont celle qui change le niveau de privilège de l’utilisateur !
4) Injection (inclusion) SQL proprement dite
Les parades à mettre en place
Précautions relevant de l’administration de base de données
- Maintenir la veille technologique (s’abonner aux alertes) et passer les patchs dès que disponibles. Cela pourra résoudre notamment les pbs de buffer overflow et autres failles système
- se baser sur la gestion des droits du SGBD plutôt que sur une gestion applicative
on y gagnera de plus une meilleure traçabilité ce sui n’est pas négligeable en cas d’attaque - mettre en place les privilèges minimums
(droits de connexion , de consultation sur qq tables sont généralement suffisants)
Précautions relevant du développement
- Masquer au maximum les paramètres
- La methode GET encode (pauvrement) les paramètres et leurs valeurs dans l’URL envoyé au serveur. La méthode POST les encode au sein du corps du message de la requête HTTP et est donc moins visible.
EN utilisant on tentera moins les utilisateurs, mais ils auront toujours la ressource de visualiser le source HTML ;-(
Bien qu’en terme de norme de programmation ce ne soit pas conseillé, on peut utiliser des nom peu explicites pour les variables sensibles - Mettre en place une gestion d’erreur non informative pour l’utilisateur final, en se réservant un niveau DEBUG très détaillé lors du développement
- sur quote
En PHP il existe désormais l’option ‘magic_quote’ qui est à ON par défaut et évite l’injection simple
Le moteur PHP substitue alors des \’ et \” , respectivement aux cotes et guillemets.
Dans d’autres environnements, On peut sur quoter manuellement les paramètres d’entrées ‘éventuellement en utilisant une fonction personnalisées) et par la invalider le SQL supplémentaire - tester que les numérique envoyés sont bien des numériques, et d’une manière générale l’authenticité du type attendu. Ceci est un corollaire du précédent !
- utiliser un niveau de privilège restreint, nécessaire et suffisant
- utiliser des procédures stockée
L’intérêt des ordres SQL paramétrés
Utiliser les ordres SQL paramétrés plutôt que les chaînes construites dynamiquement par concaténation. La structure de lordre SQL et le nombre de colonnes concernées (en consultation ou mise à jour) étant prédéfini on ne pourra pas ‘étendre’ le SQL.
exemple PHP / Oracle
{ …
// morceau de code PHP utilisant le SQL paramétré (bind variables)
$conn = OCILogon(”scott”,”tiger”);
$update = OCIParse($conn,”update emp set sal = :par_sql_sal );
OCIBindByName($update, “:par_sql_sal”, &$var_php_sal, 32);
OCIExecute($update);
…
}
mieux que :
{ …
// morceau de code PHP utilisant le SQL ‘concaténé’
$conn = OCILogon(”scott”,”tiger”);
$update = OCIParse($conn, “update emp set sal = “.$var_php_sal;);
OCIExecute($update);
…
}
en PHP / MYSQL
On utilisera en MYSQL, un tableau de variable d’entree
et le caractere ‘?’ comme marqueur de parametre dans l’ordre SQL.
Les principales fonctions MYSQLi pour gérer un ordre paramétré sont les suivantes :
mysqli_stmt_prepare(), mysqli_stmt_bind_param(),
mysqli_stmt_execute()
Voici un exemple extrait de la doc PHP (php.net)
<?
/* il y aura 4 parametres: 3 string et un double ! */
$stmt = mysqli_prepare($link, “INSERT INTO CountryLanguage VALUES (?, ?, ?, ?)”);
mysqli_stmt_bind_param($stmt, ’sssd’, $code, $language, $official, $percent);$code = ‘DEU’; $language = ‘Bavarian’;
$official = “F”; $percent = 11.2;/* execute l’ordre prepare */
mysqi_stmt_execute($stmt);printf(”%d Row inserees .\n”, mysqli_stmt_affected_rows($stmt));
/* libere les ressources */
mysqli_stmt_close($stmt);
mysqli_close($link);
?>
A noter :
Le SQL paramétré offre de plus de sérieux avantages en terme de performances puisqu’il évite de ré analyser (parsing) des ordres SQL qui font les mêmes opérations avec des variable d’entrée différentes (ce qui est très fréquent ! )
Pour terminer, la société FOUDSTONE fournit un certain nombre d’outils gratuits de vérification de sites et plus spécifiquement des outils de simulation d’injection SQL, voir HACME BOOK, HACME TRAVEL, ici : http://www.foundstone.com/us/resources-free-tools.asp