Injections SQL
Introduction
Le nom de cette technique parle d’elle-même, le but est d’injecter du code SQL afin de compromettre les requêtes vers une base de données. C’est une attaque web très connu et les répercussions peuvent être graves. Avec une bonne injection exploitant une ou plusieurs failles sur une page web, on peut aller du contournement d’authentification à la récupération complète des données.
Il faut savoir qu'il existe plusieurs types de BDD (postgresql, sqlite, mySQL), les commandes données en exemple dans cet article sont pour une base mySQL.
Exploitation
On retrouve plusieurs formes d’injection reposant sur différentes méthodes d’attaques, la plus simple étant une injection telle que : ' OR 1=1 ;#
Pour comprendre le but de cette injection, regardons une requête type pour une authentification :
SELECT * FROM user WHERE login='toto' AND passwd='frite' ;
L’utilisateur a rentré son login et son mot de passe, pour l’exemple, toto/frite. La page web forge la requête et l’envoie vers le serveur. Ce dernier va simplement exécuter la demande vers la base de données. Maintenant, que se passe-t-il si, à la place du login, on rentre l’injection :
SELECT * FROM user WHERE login='' OR 1=1 ;#' AND passwd='frite' ;
Sachant que le signe # (ou –) met en commentaire tout le reste de la requête, le serveur va interpréter :
SELECT * FROM user WHERE login='' OR 1=1 ;
On sélectionne tout de la table user avec un login vide OU la valeur true (1=1). Comme résultat, nous obtiendrons la première personne de la table user. Sur une page web sans protection, cette simple injection permet de contourner l’authentification.
Prenons l’exemple d’une page internet utilisant des paramètres dans l’url telle que :
www.exemple.com/frite/?action=register
Ici, le paramètre action cherche à lire la page register. On peut exploiter ceci avec l’opérateur AND.
www.exemple.com/frite/?action=register AND 1=1
On ajoute 1=1 afin de contrôler le résultat puisqu’il est vu comme true, donc il faut que la page register existe (c’est le cas) ET que la valeur qui suit soit true. Il reste donc à tester les injections en les faisant tester par des true ou false. Par exemple, on peut chercher la longueur du mot de passe d’un utilisateur :
www.exemple.com/frite/?action=register AND length(passwd)= 5
Si la longueur du mot de passe est bien de 5 (true), alors la page demandée s’affichera. C’est une bonne façon d’obtenir des informations. Dans cet exemple, l’étape suivante serait de comparer avec les caractères du mot de passe…
Il existe aussi une table contenant les informations sources de la base de donnée ainsi que des fonctions que l’on peut injecter afin d’obtenir des informations sur la base afin de mieux cibler les injections suivantes. Dans un cas plus réel, ce sont ces injections qu'il faut faire en premier afin d'en apprendre d'avantage sur la base de données.
Ces commandes nous donnent la version de la BDD ainsi que son nom. Ces dernières nous permettent d'obtenir le nom des colonnes ainsi que le contenu des tables.
Sécuriser les requêtes SQL
Depuis que cette faille a été découverte, des contre-mesures ont été mises en place, certaines permettent juste d’éviter les attaques SQL ‘basique’ grâce à des filtres, mais reste vulnérables à des techniques plus avancées.
Une méthode plutôt efficace consiste à changer la façon dont les requêtes sont forgées.
Sans contre-mesures :
sql.execute("SELECT * FROM user WHERE lastname = '%s'" % name)
Ici, on forge la requête avec le contenu de la variable name puis on l'envoi vers la base.
Avec contre-mesures :
sql.execute("SELECT * FROM user WHERE lastname = %s", (name,))
Alors qu'ici, on ne forge plus la requête directement, mais on envoi la forme de la requête voulue, suivi des arguments nécessaire pour cette requête.
Il existe bien des façons de protéger sa base de données, notamment en faisant attention à son code.