ECW 2020 Prequals – WEB – Casino royal

Hello,

 

Cette année j’ai de nouveau participé à l’ECW, organisé par Thalès, Airbus et Diateam.

Ce challenge était le seul (oui oui :/ ) lié réellement à la catégorie web. L’autre challenge dans la catégorie web relevait plus de la stéganographie que du web :/

Catégorie: Web

Points: Pas assez (100)

énoncé: Braquez le casino et récupérez le flag !

Voici un challenge web que j’ai mis +10h à résoudre, et dont j’ai eu le First blood ! \o/

Le challenge:

Nous avons à notre disposition un site de “casino” avec une page de login, une page pour créer un utilisateur et une page de profil pour voir les données stockées dans le profil de l’utilisateur (à savoir un email, un nom et une description) la page permet de modifier tous ces champs, et elle offre aussi la possibilité de changer le mot de passe de l’utilisateur.

Jouons un peu avec l’application:

Une fois un utilisateur créé et une fois connecté, l’application nous renvoie un token JWT:

Les liens “Home” et “Account” vérifient la validité du token JWT via la requête suivante:

Si on décode le JWT on obtient ce qui est affiché dans la réponse à l’endpoint “validate_token.php” ni plus ni moins.

La première des étapes fut de trouver des indices sur les libs et techno utilisées.

Pour ce faire, j’ai utilisé une technique que j’utilise beaucoup en bugbounty: l’injection de fautes.

En injectant des “[]” ou des “{}” dans des différents champs des différents jsons, on arrive a leak des fonction et des noms de fichiers intéressants:

 

 

Et le plus intéressant (tout est important je peux pas réduire cette image, déso):

on voit ici: “/var/www/html/api/libs/php-jwt/src/JWT.php”

Un peu d’OSINT

Utilisons Google:

Le premier lien est ce qu’on cherche ! les sources et des exemples officiels.

On peut voir dans le readme du projet un champ “Example”. Ce champ nous intéresse car quand on veut utiliser une lib ou un import quelconque dans un projet, on va souvent chercher les exemples de base pour avoir un projet qui fonctionne très vite:

J’ai donc machinalement testé la passphrase “example_key” sur le token du challenge, et bingo \o/

Donc maintenant (~2h après mon arrivée sur le challenge) j’ai donc accès a tous les utilisateurs du site, et je peux prendre n’importe quel ID.

Fin du challenge ? NOOOOON mdr, faut pas croire que c’est aussi simple.

Pour la suite, j’ai passé +9h a essayer d’énumérer, injecter et chercher ce flag, mais rien n’a fonctionné. Très frustrant.

Après quelques heures, je me suis mis a chercher des sources sur internet, une technique pour cela est de chercher le nom de fichier le plus “spécifique” de l’application. Il est évident que si je cherche “/api/login.php” je vais pas avancer bien vite, c’est trop général et trop commun comme chemin.

J’ai donc cherché:

le premier lien était très intéressant (encore une fois), étant donné que c’était exactement la même structure que le site ! :la chance:

A partir d’ici, et après avoir lu les sources, je me suis aperçu que le code était blindé. prepared statements à gogo etc. donc que faire ? et bien ne pas croire ce qu’on voit ! rien ne nous dit que les sources sont exactement les mêmes que le challenge !

Donc j’ai commencé a essayer de rentrer quelques vulnérabilités, notamment –ma préférée– les SQLi.

Pour cela, j’ai commencé par étudier les sources, je cherchais des endroits où il y avait évidemment des requêtes SQL.

Cette portion de login était intéressante:

Cela menait à cette portion dans api/user.php:

Donc, si le code n’est pas le même que dans le repository github public, il pourrait y avoir une SQLi ici.

Je m’explique:

Premier screen -> récup de l’email:

  • On récupère l’input de l’utilisateur
  • On parse l’email et on l’envoie dans la fonction emailExists()

Second screen -> prepared statement:

  • On crée une query SQL,
  • On la prépare avec les valeurs de l’email,
  • On exécute la query sql

Donc si les sources sont pas les mêmes, l’auteur du challenge à probablement du enlever le prepared statement.

J’ai donc pondu un quick script python (dégeulasse oui.) pour update un compte avec les champs name, description, email et password, et tenter de me connecter avec:

Etant donné qu’il n’y a pas de retour je ne vais pas avoir le choix: SQLi time based ou boolean blind. J’ai opté pour la time based, parce que j’aime beaucoup les timebased.

Pour tester rapidement et efficacement une SQLi blind, j’utilise ma wordlist custom d’exploit des SQLi time based (toutes -ou presque- mes wordlists custom sont presque toutes publiques ici: https://github.com/ElSicarius/SuperTruder/tree/master/database ).

Au bout de quelques secondes, et sur le dernier payload de ma liste:

SLEEP(10)/*' or SLEEP(10) or '" or SLEEP(10) or "*/

la requête à simplement timeout, bingo ? vérifions:

SLEEP(0)/*' or SLEEP(10) or '" or SLEEP(10) or "*/ -> sleep

SLEEP(10)/*' or SLEEP(0) or '" or SLEEP(10) or "*/ -> no sleep

' or SLEEP(10) or ' -> sleep

' or if(1=1, sleep(10), sleep(0)) ' -> sleep

' or if(1=2, sleep(10), sleep(0)) ' -> no sleep

Okay ! donc la sqli est con.fir.mée 🙂 passons à l’exploit.

Exploit

On va utiliser le même script, avec juste un for i in string.printable en plus.

On va mettre chacun de ces payloads dans le champ email et itérer sur les lettres comparées :

' or if(substring((select table_name from INFORMATION_SCHEMA.TABLES limit 0,1),1,1)="u",sleep(10),sleep(0)) or ' -> ici on regarde les noms des tables

On trouve après énumération la table “user” et la table “flag

' or if(substring((select columns_name from INFORMATION_SCHEMA.columns where table_name='flag' limit 0,1),1,1)="u",sleep(10),sleep(0)) or ' -> ici on regarde les noms des colonnes

' or if(substring((select flag from flag ),1,1)="u",sleep(10),sleep(0)) or ' -> on extrait le flag :)

 

Voilou, c’était -pas trop- compliqué comme challenge, mais j’ai été induit en erreur trop longtemps par les sources que je croyais vraies :/

 

La bise 😀 .

 

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *