본문 바로가기
Security/War Games

[Dreamhack Lv.1] csrf-1

by seoyamin 2024. 12. 20.

https://dreamhack.io/wargame/challenges/26

 

csrf-1

여러 기능과 입력받은 URL을 확인하는 봇이 구현된 서비스입니다. CSRF 취약점을 이용해 플래그를 획득하세요. 문제 수정 내역 2023.07.18 css, html 제공 및 read_url() 코드 일부가 변경되었습니다. Referen

dreamhack.io

 

문제 설명

여러 기능과 입력받은 URL을 확인하는 봇이 구현된 서비스입니다.

CSRF 취약점을 이용해 플래그를 획득하세요.

 


문제 풀이

vuln, memo, notice flag, flag 페이지로 구성되어 있다.

 

1. vuln page

'script' 문자열이 취약성을 갖고 있으므로 '*'로 자동 치환되는 것을 확인할 수 있다.

script 말고 다른 방법을 이용해야 한다는 점을 기억하고 넘어가자.

@app.route("/vuln")
def vuln():
    param = request.args.get("param", "").lower()
    xss_filter = ["frame", "script", "on"]
    for _ in xss_filter:
        param = param.replace(_, "*")
    return param

 

2. memo

parameter 'memo'로 넘긴 키워드가 누적되며 저장됨을 확인할 수 있다.

내가 입력한 값이 출력되는 공간임을 기억하고 넘어가자.

@app.route("/memo")
def memo():
    global memo_text
    text = request.args.get("memo", None)
    if text:
        memo_text += text
    return render_template("memo.html", memo=memo_text)

 

3. notice flag

접근 권한이 걸려있는 페이지이다.

코드를 보면 userid를 admin으로 설정하여 뚫어볼 수 있을 것 같다.

@app.route("/admin/notice_flag")
def admin_notice_flag():
    global memo_text
    if request.remote_addr != "127.0.0.1":
        return "Access Denied"
    if request.args.get("userid", "") != "admin":
        return "Access Denied 2"
    memo_text += f"[Notice] flag is {FLAG}\n"
    return "Ok"

 

4. flag

입력한 param값이 전달되는 공간이다.

 

코드를 보면 아래와 같은 플로우로 진행됨을 알 수 있다.

1st. POST 메소드 호출 시 check_csrf (param) 호출한다.
2nd. check_csrf에서 /vuln에서 param 명령어를 실행하는 url을 만든다.
3rd. check_csrf에서 만든 url을 가지고 read_url(url, cookie) 호출한다.
4th. read_url에서 cookie의 도메인을 127.0.0.1로 변경하고 url 실행 결과를 반환한다.

 

def read_url(url, cookie={"name": "name", "value": "value"}):
    cookie.update({"domain": "127.0.0.1"})
    try:
        service = Service(executable_path="/chromedriver")
        options = webdriver.ChromeOptions()
        for _ in [
            "headless",
            "window-size=1920x1080",
            "disable-gpu",
            "no-sandbox",
            "disable-dev-shm-usage",
        ]:
            options.add_argument(_)
        driver = webdriver.Chrome(service=service, options=options)
        driver.implicitly_wait(3)
        driver.set_page_load_timeout(3)
        driver.get("http://127.0.0.1:8000/")
        driver.add_cookie(cookie)
        driver.get(url)
    except Exception as e:
        driver.quit()
        print(str(e))
        # return str(e)
        return False
    driver.quit()
    return True


def check_csrf(param, cookie={"name": "name", "value": "value"}):
    url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"
    return read_url(url, cookie)


@app.route("/flag", methods=["GET", "POST"])
def flag():
    if request.method == "GET":
        return render_template("flag.html")
    elif request.method == "POST":
        param = request.form.get("param", "")
        if not check_csrf(param):
            return '<script>alert("wrong??");history.go(-1);</script>'

        return '<script>alert("good");history.go(-1);</script>'

 

5. notice_flag

화면상에는 보이지 않지만, 코드를 보면 /admin/notice_flag API가 열려있음을 확인할 수 있다.

해당 메서드는 request의 remote_addr이 127.0.0.1이며 userid가 admin일 때 FLAG를 출력한다.

우리가 이번 문제를 풀기 위해서는 이 메서드를 이용해야 함을 알 수 있다.

 

@app.route("/admin/notice_flag")
def admin_notice_flag():
    global memo_text
    if request.remote_addr != "127.0.0.1":
        return "Access Denied"
    if request.args.get("userid", "") != "admin":
        return "Access Denied 2"
    memo_text += f"[Notice] flag is {FLAG}\n"
    return "Ok"

 

Sol.

  • script를 사용할 수 없으므로 img나 div와 같은 다른 태그를 이용하자
  • memo는 결과가 출력되는 공간이므로 찾고자 하는 flag를 memo 페이지에 출력하게 하자
  • flag의 입력값이 vuln을 거쳐 실행되므로 flag > vuln > notice_flag 실행 > memo에 출력 플로우를 구성해보자

 


결과

아래의 스크립트를 flag의 param에 넣어주자, memo 페이지에 FLAG가 출력되었다.

<img src=/admin/notice_flag?userid=admin>

 

'Security > War Games' 카테고리의 다른 글

[Dreamhack Lv.1] XSS-1  (0) 2024.12.20
[Dreamhack Lv.1] csrf-2  (0) 2024.12.20
[Dreamhack Lv.1] simple_sqli  (0) 2024.12.20