L’agence s’SQL risque
- Difficulty
-
easy - Category
-
web - Points
- 50
- Solves
- 12
- Solved in time?
-
yes - Flag
-
HackHer{N0t_ThaT_H4rD}
Notre agence est persuadée d’être totalement sécurisée. Prouvez-lui le contraire en vous connectant à l’espace de monitoring de l’agence en récupérant le nom de la mission classifiée.
Format : HackHer{M1ss1on}
En ouvrant le site, on est accuillies par un site aussi beau qu’inutile (aucun bouton ne fonctionne), mais un bouton est intéressant : “Accès Sécurisé”, menant à la page /login.html

En remplissant le formulaire avec des données aléatoires, on peut vérifier quelles informations sont vraiment utilisées en regardant la requête et sa réponse dans les outils de développement (onglet réseau) :

On voit donc qu’il n’y a pas besoin du niveau d’habilitation ni de la 2FA, seulement l’identifiant et le code d’accès. Il n’y a évidemment pas non plus d’authentification biométrique, et la connexion n’est certainement pas sécurisée par chiffrement AES-256.
Dans ce contexte de formulaire sur une épreuve web, la première idée qui vient à l’esprit est de tenter une injection SQL. On peut confirmer la faisabilité en soulevant une erreur SQL et en regardant la réponse dans un premier temps.
En rentrant ' a dans le champ de l’identifiant, on obtient une erreur SQL :

Bingo !
On peut alors créer notre injection :
admin' or 1=1; --L'injection SQL, comment ça marche ?
Je ne vais pas vous apprendre le fonctionnement de SQL, mais on peut parler rapidement des injection SQL avec du code simple et détaillé.
Dans ce contexte, la requête SQL pour vérifier les identifiants rentrés doit ressembler à ça :
SELECT * FROM users WHERE username = '<input_username>' AND password = '<input_password>';Si un résultat est retourné, alors l’utilisateur avec ce nom existe, et il a rentré le bon mot de passe (tous les champs sont vérifiés). Le serveur peut alors autoriser l’accès à la page suivante. Si aucun résultat n’est retourné, alors soit le nom d’utilisateur n’existe pas, soit le mot de passe est incorrect, et l’accès est refusé.
Sur le serveur, le code du backend doit ressembler à ça, avec des variables pour les champs du formulaire :
$username = $_POST['username'];$password = $_POST['password'];$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password';";$result = execute_query($query);if ($result) { // Accès autorisé} else { // Accès refusé}Il nous faut alors un moyen de retourner un résultat à tous les coups, sans connaitre ni identifiant, ni mot de passe. Il faudrait alors modifier la requête SQL directement. Mais comment faire, si on y a pas accès ?
C’est la qu’intervient l’injection SQL : on peut injecter dans nos champs de formulaire les bons caractères pour faire en sorte que la requête SQL soit modifiée, et retourne un résultat à tous les coups.
Par exemple, si on rentre admin' OR 1=1; -- dans le champ de l’identifiant, et n’importe quoi dans le champ du mot de passe, voici ce qu’il se passe :
SELECT * FROM users WHERE username = '$username' AND password = '$password';↓ │ └───────┐ │ │SELECT * FROM users WHERE username = 'admin' OR 1=1; --' AND password = 'something';Le contenu de la variable $username a été écrit telle quel dans la requête, la modifiant à notre avantage.
Décomposons notre injection :
admin: le nom d’utilisateur, mais il n’importe pas, puisque c’est la suite qui va nous garantir un accès': ce caractère permet de fermer la chaîne de caractères dans laquelle le nom d’utilisateur est censé être, pour pouvoir injecter du code SQL juste aprèsOR 1=1: cette condition est toujours vraie, quelques soient les autres conditions, elle garantit que la requête retourne au moins un résultat, même si le nom d’utilisateur n’existe pas;: ce caractère permet de terminer la requête SQL, pour éviter que le reste de la requête ne soit exécuté--: ce sont des commentaires en SQL, tout ce qui suit est ignoré, c’est une sécurité qui permet d’ignorer la partie de la requête qui vérifie le mot de passe (ou tout autre condition qui pourrait poser problème)
La requête retournant maintenant un résultat, le site nous laisse accéder à la page sécurisée.
Un peu de lecture : https://book.hacktricks.wiki/en/pentesting-web/sql-injection/index.html
On a alors accès à la page sécurisée /user.php, qui contient la mission recherchée :

L’inconnu devenu famous
- Difficulty
-
medium - Category
-
web - Points
- 50
- Solves
- 11
- Solved in time?
-
yes - Flag
-
HackHer{T3m3raire_SQL_R34L_D4T4B4S3}
Un ami, devenu complètement fan du Téméraire après l’annonce du Président, a décidé de créer un site dédié à ce sous-marin. Il vous assure que celui-ci est totalement sécurisé.
Retrouvez le flag.
Format : HackHer{Fl4g}
Exploration
De la même manière que le premier challenge, en ouvrant ce site, on est à nouveau accueillies par un site aussi beau qu’inutile, et cette fois-ci le seul élément intéressant est la barre de recherche permettant d’interroger la base de données :

Des propositions de recherches sont listées sous la barre de recherche, et seulement 2 d’entre elles ne retournent un résultat valide.
En regardant la requête dans l’onglet réseau, on se rend compte que la requête retourne une liste de plusieurs résultats potentiels, eux-mêmes formés d’un champ title et text :

La différence avec le challenge précédent, c’est que cette fois-ci, il ne suffit pas d’obtenir un résultat pour accéder à une page secrète. D’ailleurs, on ne même pas où se trouve le flag. Peut être est-ce une entrée de la table qui est interogée dans la barre de recherche, ou bien est-ce même une autre table.
Dans tous les cas, l’objectif cette fois va être de récupérer la structure de la base de données pour récupérer des informations.
Identification
Comme pour le challenge précédent, la première étape est de tenter une injection SQL pour voir si c’est possible. En rentrant ' a dans la barre de recherche, on obtient une erreur SQL :

Maintenant, il nous faut identifier le logiciel de base de données utilisé, pour pouvoir utiliser la bonne syntaxe. Pour cela, il existe différentes méthodes consistant à exécuter des fonctions ou commandes spécifiques à chaque logiciel, et à observer si elles fonctionnent ou non pour en déduire le logiciel utilisé.
| Logiciel (Engine) | Recherche (injection) | Erreur |
|---|---|---|
| MySQL | ' or connection_id()=connection_id(); -- | General error: 1 no such function: connection_id |
| Oracle | ' or RAWTOHEX('AB')=RAWTOHEX('AB'); -- | General error: 1 no such function: RAWTOHEX |
| PostgreSQL | ' or current_database()=current_database(); -- | General error: 1 no such function: current_database |
| SQLite | ' or sqlite_version()=sqlite_version(); -- | Résultat valide |
| MSAccess | ' or val(cvar(1))=1; -- | General error: 1 no such function: cvar |
Grâce à ces tests, on peut voir que la fonction sqlite_version() est reconnue, et donc que le logiciel de base de données utilisé est SQLite !
Injection
Cette fois-ci, nous allons utiliser une injection SQL un peu plus avancée que la précédente, qui va nous permettre de faire une union entre notre requête de recherche et une requête qui va nous permettre de récupérer des informations sur la base de données.
Seulement, pour qu’une union fonctionne, il faut que les deux requêtes retournent le même nombre de champs. Sans ça, les requête ne peuvent pas être combinées, résultat en une erreur.
On a vu plus haut que la requête de recherche ne retourne que 2 champs : title et text. Il va donc falloir faire en sorte que notre requête d’union retourne également 2 champs, même si ces champs ne sont pas forcément utilisés.
Commençons par récupérer les différentes tables disponibles. Avec SQLite, c’est possible grace à “super table” sqlite_schema :
aaa' UNION SELECT sql, 'x' FROM sqlite_schema; --Comme tout à l’heure, décomposons notre injection :
aaa: le terme de recherche, qui n’a pas d’importance, et qui peut même être laissé vide': on ferme la chaîne de caractères du terme de rechercheUNION SELECT: on fait une union entre la requête de recherche et notre requête personnaliséesql, 'x' FROM sqlite_schema: on sélectionne le champsqlde la tablesqlite_schema, qui contient les requêtes de création des différentes tables, et un champ fictif'x'vide pour que notre requête retourne 2 champs.; --: on termine la requête et on commente le reste pour éviter les erreurs
Pour votre plaisir personnel, la requête SQL générée par cette injection ressemble à ça :
SELECT name, description FROM data WHERE name LIKE '%$search';↓ │ └──────────────────────────────────────────┐SELECT name, description FROM data WHERE name LIKE '%aaa' UNION SELECT sql, 'x' FROM sqlite_schema; --%';En exécutant cette injection, on obtient le résultat suivant :

On voit bien la table SONAR_DATA(name TEXT, description TEXT) qui est interrogée pour les recherche, ainsi qu’une deuxième table SECRET_VAULT(flag_key TEXT, flag_val TEXT) qui semble intéressante !
Il ne reste alors plus qu’à faire la requête qui va intéroger cette table pour récupérer le flag :
aaa' UNION SELECT flag_key, flag_val FROM secret_vault; --Tada !
