Basics

L’injection SQL (ou SQLi) est une attaque dont le but est d’utiliser l’inattention des développeurs web afin d’exploiter une base de données. Les failles SQLi reposent bien évidemment sur la langage SQL, il est donc nécessaire de connaître les rudiments de celui-ci. Afin de rendre le tout plus compréhensible étudions le fonctionnement d'un formulaire d'authentification vulnérable.

Imaginez que vous voulez vous connecter sur un forum, vous allez avoir un formulaire ressemblant plus ou moins à ça :

sqlbypass1.png

Dont le code HTML est le suivant:

<form action="traitement.php" method="post">
  <label>Entrez votre identifiant</label>
  <input type="text" name="username"/>
  <br>
  <label>Entrez votre mot de passe</label>
  <input type="text" name="password"/>
  <br>
  <input type="submit"/>
</form>

Au niveau de la base de données nous n'aurons qu'une seule table "utilisateur" qui contient l'identifiant du compte, un identifiants, un mot de passe et une adresse mail:

create table users
(
	idAccount not null auto_increment primay key,
	username not null varchar(20),
	password not null varchar(30),
	email not null varchar(50)
)
ENGINE=INNODB;

Qui contiendra en tout deux utilisateurs : un administrateur et un utilisateur simple:

INSERT INTO users (username, password, email) values ("admin", "somecomplicatedpassword", "administrator@whiteflag.blog");
INSERT INTO users (username, password, email) values ("user1", "azerty", "user1@whiteflag.blog");

Côté serveur nous aurons le fichier traitement.php qui va recevoir le contenu des variable username et password puis les traiter via le code suivant:

<?php
try{
	$bdd = new PDO("mysql:host=localhost;dbname=sql", "root", "", array(PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING));
}
catch (Exception $e){
	die ("Error : ".$e->getMessage());
}

$req = $bdd->query("SELECT * FROM users WHERE username = ".$_POST["username"]." AND password = ".$_POST["password"].";");
while ($results = $req->fetch_all()){
	$username = $results["username"];
}

if (isset($username)){
	echo "Bienvenue : ".$username;
}
else {
	echo "Authentication failed";
}

Du coup si dans le formulaire d'authentification j'entre le username "admin" et le mot de passe "somecomplicatedpassword" je vais être authentifié en tant qu'administrateur :

bienvenue.png

Que se passerait-il si au lieu d'entrer un couple d'identifiant/mot de passe j'entrais du code SQL ? Eh bien je pourrais manipuler la requête SQL et faire en sorte de me connecter en tant qu'administrateur. Ainsi en entrant le username:

admin'; --

Et le mot de passe:

whiteflagcestlfeu

Je vais atterrir sur l’espace administrateur :

bienvenue

Pourquoi est-ce que ça fonctionne ? Pour comprendre il est nécessaire d'analyser la structure de la requête SQL:

SELECT * FROM users WHERE username = ".$_POST["username"]." AND password = ".$_POST["password"].";

En utilisant les inputs précédemment vus, nous avons transformé la requête exécutée en::

SELECT * FROM users WHERE username = 'admin' ; -- password = 'whiteflagcestlfeu';

Avec cette requête, on demande au SGBD de voir s’il existe un utilisateur dont l’identifiant est ‘admin’ et le mot de passe est "whiteflagcestlfeu". Le souci c'est qu'en plus de ces données nous avons aussi ajouté des caractères spéciaux dont le caractèree caractère "--" qui va indiquer au SGBD que tout ce qui se trouve derrière ces caractères doit être considéré comme un commentaire. Au final la requête qui exécutée sera la suivante:

SELECT * FROM users WHERE username = 'admin' ;

Cette requête va tout simplement rechercher si dans la base de données il existe un utilisateur dont le username est "admin". Comme c'est le cas l'application va nous logger en tant qu'admin ce qui nous aura permis de bypass l'authentification.

Seulement voilà ici nous avons pu nous connecter au compte admin c'est parce que nous savions qu'il existe un username "admin" dans la base de données. Comment aurait-on fait si on ne l’avait pas eu ? Eh bien voici les inputs que nous aurions dû utiliser:

sqlbypass2.png

Et le résultat :

sqlbypass3.png

Comme vous pouvez le voir nous ne sommes pas logué en tant qu'admin mais en tant qu'user1. Pourquoi ça marche ? Tout simplement parce que dans ce cas là, nous n’avons pas spécifié d’username en particulier mais nous avons fait en sorte que le résultat de la requête soit toujours vrai (à l’aide du « 1=1 ») :

SELECT * FROM users WHERE '' or 1=1 ; --

Comme la requête est toujours vraie, l'application va nous authentifier en nous attribuant les droits du dernier utilisateur de la base de données soit user1.