---
# System prepended metadata

title: Curlove

---

# Curlove


#### app.py

```

from flask import Flask, request, render_template, session, redirect, abort
from query import get_user, register_user
from hashlib import sha256
from re import search
from os import urandom
import subprocess


app = Flask(__name__)
app.secret_key = urandom(32)

try:
    FLAG = open("./flag.txt", "r").read()
except:
    FLAG = "DH{{This_is_flag}}"


@app.route("/", methods=["GET"])
def index():
    if not session:
        return render_template("login.html")
    
    elif session["isAdmin"] == True:
        return render_template("admin.html")
    
    else:
        return render_template("guest.html")


@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "GET":
        return render_template("login.html")

    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")

        if username == "" or password == "":
            return render_template("login.html", msg="Enter username and password")

        sha256_password = sha256((password).encode()).hexdigest()
        try:
            user = get_user(username, sha256_password)

            if user:
                if user[1].startswith("admin"):
                    session["username"] = user[1]
                    session["isAdmin"] = True
                    session["login"] = True
                    return redirect("/admin")
                else:
                    session["username"] = user[1]
                    session["isAdmin"] = False
                    session["login"] = True
                    return redirect("/guest")
            else:
                return render_template("login.html", msg="Login Failed..."), 401
        except Exception as e:
            abort(500)


@app.route("/signup", methods=["GET", "POST"])
def signup():
    if request.method == "GET":
        return render_template("signup.html")

    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")

        if username == "" or password == "":
            return render_template("login.html", msg="Enter username and password")

        m = search(r".*", username)

        if username or m:
            if m.group().strip().find("admin") == 0:
                return render_template("signup.html", msg="Not allowed username"), 403
            else:
                username = username.strip()
                sha256_password = sha256((password).encode()).hexdigest()
                register_user(username, sha256_password)
                return redirect("/login")


@app.route("/guest", methods=["GET"])
def guest():
    if not session:
        return redirect("/login")

    return render_template("guest.html")


@app.route("/admin", methods=["GET", "POST"])
def admin():
    if not session:
        return redirect("/login")
    
    if session["isAdmin"] == False:
        return redirect("/guest")

    if request.method == "GET":
        return render_template("admin.html")

    if request.method == "POST":
        url = request.form["url"].strip()

        if (url[0:4] != "http") or (url[7:20] != "dreamhack.io/"):
            return render_template("admin.html", msg="Not allowed URL")

        if (".." in url) or ("%" in url):
            return render_template("admin.html", msg="Not allowed path traversal")
        
        if url.endswith("flag") or ("," in url):
            return render_template("admin.html", msg="Not allowed string or character")
        try:
            response = subprocess.run(
                ["curl", f"{url}"], capture_output=True, text=True, timeout=1
            )
            return render_template("admin.html", response=response.stdout)

        except subprocess.TimeoutExpired:
            return render_template("admin.html", msg="Timeout !!!")


@app.route("/flag", methods=["GET"])
def flag():
    ip_address = request.remote_addr
    if ip_address == "127.0.0.1":
        return FLAG
    else:
        return "Only local access allowed", 403


app.run(host="0.0.0.0", port=80)
```


Nhìn sơ qua về đoạn code có vẻ ta phải đăng nhập bằng ``` username``` bắt đầu với chuỗi ```admin``` để có thể get ```/admin```


```
   if user:
                if user[1].startswith("admin"):
                    session["username"] = user[1]
                    session["isAdmin"] = True
                    session["login"] = True
                    return redirect("/admin")
                else:
                    session["username"] = user[1]
                    session["isAdmin"] = False
                    session["login"] = True
                    return redirect("/guest")
```

Tuy nhiên khi để ý đến đoạn ```/signup```, có vẻ ta đã bị filter signup với ```admin``` bằng regex ``` search(r".*", username)```

```
@app.route("/signup", methods=["GET", "POST"])
def signup():
    if request.method == "GET":
        return render_template("signup.html")

    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")

        if username == "" or password == "":
            return render_template("login.html", msg="Enter username and password")

        m = search(r".*", username)

        if username or m:
            if m.group().strip().find("admin") == 0:
                return render_template("signup.html", msg="Not allowed username"), 403
            else:
                username = username.strip()
                sha256_password = sha256((password).encode()).hexdigest()
                register_user(username, sha256_password)
                return redirect("/login")
```

```m = search(r".*", username)``` sẽ tìm kiếm xem trong biến username có bất kỳ chuỗi ký tự nào hay không. Nếu có, m sẽ chứa thông tin về kết quả của việc tìm kiếm, bao gồm vị trí xuất hiện của chuỗi ký tự trong username. Nếu không tìm thấy bất kỳ chuỗi ký tự nào, m sẽ là None.
 ```m.group().strip().find("admin")``` sẽ tiếp tục xử lý trên kết quả của việc tìm kiếm từ biểu thức chính quy ở đoạn trước (m = search(r".*", username)).

Đầu tiên, m.group() sẽ trả về chuỗi kết quả từ việc tìm kiếm biểu thức chính quy. Sau đó, strip() sẽ loại bỏ khoảng trắng (nếu có) ở đầu và cuối chuỗi kết quả. Cuối cùng, find("admin") sẽ tìm kiếm chuỗi "admin" trong chuỗi kết quả đã được xử lý từ trước đó.

Hmm, có vẻ lại là regex và theo kinh nghiệm của mình thì việc đơn giản nhất bypass qua những filter kiểu này là dùng ```\n```, regex sẽ chỉ check các kí tự ở dòng đầu tiên và ta dễ dàng bypass
![image](https://hackmd.io/_uploads/Sk2rM38Dp.png)

Login successfully !!! Bây giờ đi tới cách  ```bypass SSRF with WAF``` 


```
if request.method == "POST":
        url = request.form["url"].strip()

        if (url[0:4] != "http") or (url[7:20] != "dreamhack.io/"):
            return render_template("admin.html", msg="Not allowed URL")

        if (".." in url) or ("%" in url):
            return render_template("admin.html", msg="Not allowed path traversal")
        
        if url.endswith("flag") or ("," in url):
            return render_template("admin.html", msg="Not allowed string or character")
        try:
            response = subprocess.run(
                ["curl", f"{url}"], capture_output=True, text=True, timeout=1
            )
            return render_template("admin.html", response=response.stdout)

        except subprocess.TimeoutExpired:
            return render_template("admin.html", msg="Timeout !!!")
```


Hmm, có vẻ flag nằm ở ```/flag``` tuy nhiên ta bị chặn dùng LFI rồi thêm nữa url phải chứa ```dreamhack.io``` mà flag lại nằm ở ```local```:

```
@app.route("/flag", methods=["GET"])
def flag():
    ip_address = request.remote_addr
    if ip_address == "127.0.0.1":
        return FLAG
    else:
        return "Only local access allowed", 403
```
Sau một vài đường google thì mình tìm được một kỹ thuật khá hay ho đó là sử dụng ``` URL globbing ```. Ta có thể sử dụng ```{}``` hoặc  ```[]``` để bypass qua WAF: 

- ```[]```: You can ask for a numerical range with [N-M] syntax, where N is the start index and it goes up to and including M
- ```{}```: Sometimes the parts do not follow such an easy pattern, and then you can instead give the full list yourself but then within the curly braces instead of the brackets used for the ranges:
```curl -O "http://example.com/{one,two,three,alpha,beta}.html"```

https://everything.curl.dev/cmdline/globbing


![image](https://hackmd.io/_uploads/BycoVnIw6.png)

Alright vậy là ta dễ dàng thực thi được path traversal thông qua việc sử dụng kỹ thuật này.
Theo đó thì

- ../ => {.}./    
- Chúng ta có thể bypass check  ```endswith('flag')```: flag => fla{g} or fla[gg].


Tuy nhiên làm thế nào để call được local đây, ta bị đắt buộc dùng ```dreamhack.io``` ở url, mình mất một lúc suy nghĩ và nhớ đến việc sử dụng ```@/```


![image](https://hackmd.io/_uploads/H1UE8hIDp.png)
Vậy call localhost thế nào, sử dụng kỹ thuật trên thì ta có thể áp dụng ```http@0/``` trong một số trường hợp dùng 0 cũng sẽ trả về ip ```0.0.0.0```

Và payload cuối cùng mình dùng là:
```http@0/dreamhack.io/{.}./fla{g}```


and we have the flag:


![image](https://hackmd.io/_uploads/BJ36P3Lvp.png)



