안녕 오늘은 드림핵 ‘curl’ + ‘ove’ 문제를 풀어보자. 난이도가 5로 상당히 높게 책정되었는데, 난이도에 걸맞게 크게 두가지 파트로 나누어진 문제였다.
이 문제에서 주는 인사이트는 정규식 우회와 curl의 메타기호와 url의 구조이다.
먼저 문제에서 다운로드 받은 app.py의 코드를 살펴보면,
try: FLAG = open("./flag.txt", "r").read() except: FLAG = "DH{{This_is_flag}}"
플래그의 위치는 현재 디렉터리인 것을 알 수 있다.
@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 !!!")
위의 코드를 보면, admin 권한이 있어야, 들어갈 수 있는 관리자 페이지가 존재한다.
일단 admin 권한을 얻을 수 있는 방법을 보면,
@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")
코드처럼 admin으로 시작하는 아이디의 경우 자동으로 관리자 권한을 할당 받게 되는데, sign up 시에, admin이 포함된 단어를 필터링하는 정규식이 존재한다.
하지만, 정규식에 “.”은 개행 문자를 제외한 문자를 타겟으로하는데 취약점이 있다.
![](https://apple4n6.com/wp-content/uploads/2023/12/image.png)
그렇다면 아이디의 처음을 개행 문자로 하는 아이디를 만든다면 필터링을 우회할 수 있다!
URL 인코딩으로 특수 문자를 변환하므로, Burp Suite를 이용해서 직접 개행 문자를 넣어준다.
![](https://apple4n6.com/wp-content/uploads/2023/12/image-3.png)
이 방법으로 Admin page에 접근할 수 있었다.
접근한 Admin page에서는 url을 입력할 수 있는데, 이때 입력 값을 체크하는 기능이 있다.
@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 !!!")
Url 입력 값을 체크하는 부분을 정리하면 다음과 같다.
- http 프로토콜이 [0:4]에서 나와야하고, dreamhack.io/가 [7:20]까지 나와야 한다.
- Path traversal을 막기 위해서 “..” “%”필터링이 있다.
- flag나 , 같은 문자로 끝나는 것을 막는다.
이런 조건들을 통과하면 비로소 입력한 url을 curl 명령어를 통해서 실행하고 페이지에 띄우게 된다.
![curl 실행 예](https://apple4n6.com/wp-content/uploads/2023/12/image-6.png)
해당 조건을 만족하는 url을 좀 생각해보면, 일단 [4:6] 까지가 우리가 사용할 수 있는 url 공간이다.. 3글자 밖에 없는데, 이 3자리를 이용하거나 자리를 늘릴 방안을 생각해야 한다.
![](https://apple4n6.com/wp-content/uploads/2023/12/image-7.png)
curl은 프로토콜을 추측해서 적용해주는 기능이 있다 생략하면 http를 적용해줄 듯하다.
![](https://apple4n6.com/wp-content/uploads/2023/12/image-4.png)
좋은 힌트를 참고한다.
ip주소 0.0.0.0은 인트 값으로 계산하면 결국 0이다. 즉 내가 가진 모든 ‘로컬주소’를 지칭하는 url 0.0.0.0은 0으로 써도 되는 것이다.
flag.txt를 필터링을 우회하며 불러내기 위해서 중괄호를 씌웠다. 이 코드가 왜 작동할 수 있냐하면 바로 Crul의 글로빙 기능 덕분이다.
![](https://apple4n6.com/wp-content/uploads/2023/12/image-8-1024x730.png)
이렇게 해서 완성된 url을 입력하면 flag가 나온다
http@0/dreamhack.io/.{.flag.txt}
![](https://apple4n6.com/wp-content/uploads/2023/12/image-5.png)