Web Hacking/DreamHack

드림핵 XSS Filtering Bypass Advanced 풀이

프레딕 2023. 12. 29. 02:30
728x90
#!/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)

코드가 꽤 길다 각 페이지 별로 설명을 해보자면

- /vuln 페이지

get요청으로 param을 받고 xss_filter 함수에서 필터링을 진행 한 뒤, 결과값을 바로 리턴한다.

-/flag 페이지

get요청은 flag.html파일을 열고

post요청을 보냈을 때, check_xss함수를 call한다.

check_xss 함수를 쭉 올라가보면 read_url함수를 호출해 vuln 엔드포인트에 접속하는 것을 알 수 있다.

-/memo

memo파라미터를 받고 memo_text변수에 넣은 뒤 render_template함수를 호출해 렌더링 하여 리턴한다.

 

취약점을 좀 보자면은 먼저 vuln페이지에서 결과값을 바로 리턴하기 때문에 xss 취약점이 발생할 수 있을 것 같다.

memo페이지는 render_template함수를 사용하기 때문에 뚫기 쉽지 않아 보인다.

그래서 먼저 vuln페이지를 통해 xss 공격을 시도해볼 것이다.

 

xss_filter를 보면은 script,on,javascript, window, self, .... 등등 여러가지가 필터링 되는걸 알 수 있다.

그래서 대부분의 방법은 막힐 것이고 그렇다면 생각나는 방법이 uri 인코딩이 있다. 

출처 :&nbsp;https://dreamhack.io/forum/qna/2050/

저 네줄의 스크립트 중 URI라고 표시되어 있는 곳은 uri normalize를 거친다고 한다.

1. \n, \t 등이 제거된다.

2. %03처럼 퍼센트 인코딩된 것을 바꿔준다. %03은 1바이트 값 0x03으로 바꾼다.

 

그리고 스크립트를 사용해야 하기 때문에 저 네 줄 중 iframe을 사용해주도록 하겠다. 왜냐하면 iframe태그의 src속성은 URL을 인자로 받기 때문에 활성 하이퍼링크를 이용해 자바스크립트 코드를 삽입하는것이 가능하기 때문이다.

ex) <iframe src="javascript:alert(1)">

 

그렇다면 xss 공격이 되는지 위의 alert코드를 삽입해보도록 하겠다.

그냥 삽입하면 script 등 필터링에 걸리니 uri normalize를 이용해 우회해보겠다.

<iframe src="javas%09cript:alert%25281%2529">

먼저 %09는 tab이기 때문에 위의 1번 규칙으로 인해 제거된다. 그리고 %2528과 %2529는 각각 '(' 과 ')'를 뜻하는 퍼센트 인코딩 값이다. 

아스키 코드표를 보면은 '('는 단순 %28이지만 위의 문제는 '('와 ')'를 필터링하고 있기 때문에 이중 인코딩을 사용해야 한다. 이중인코딩이란 %28을 한번더 인코딩한건데 %가 %25이기때문에 %를 인코딩하여 %2528이된것이다.

https://www.bugbountyclub.com/pentestgym/view/39

 

인코딩(Encoding) | Pentest Gym | 버그바운티클럽

이번 훈련에서는 웹 애플리케이션에서 사용되는 여러가지 인코딩 기법을 다룹니다.인코딩(Encoding)이란?인코딩이란 어떤 한 형식의 데이터를 다른 형식으로 변환하는 기술이나 과정

www.bugbountyclub.com

위는 이중 인코딩 참고 사이트이다.

 

결국엔 alert가 잘 작동되는걸 확인했다. 그렇다면 flag를 확인해줘야 하는데 flag의 값을 memo 페이지에 나오게 할 것이다.

먼저 vuln 페이지에서 

<iframe src="javas%09cript:locatio%09n.href='/memo?memo='%2bdocumen%09t.cookie">

이렇게 입력하면 memo페이지가 나오는걸 알 수 있다. (%2b는 +)
현재 페이지에선 cookie의 값이 아무것도 없기 때문에 memo페이지에 공백만 추가된것이 보인다. 우리는 그 위의 쿠키값을 얻어야 하기 때문에 flag페이지에서 진행해줘야 한다.

하지만 flag페이지에 위와 똑같이 치면은 flag가 안나온다. 

그 이유는 인코딩 방식에 있다.

check_xss 함수에서 urlib.parse.quote 함수를 사용했는데 이는 url 인코딩 함수이다. 

이 함수에서 인코딩을 해주기 때문에 우리가 굳이 인코딩된 값을 안써줘도 된다. 괜히 써봤자 이중인코딩이 되어 flag가 안나오게 된다.

때문에 <iframe src="javas    cript:locatio    n.href='/memo?memo='+documen    t.cookie">

이렇게 인코딩이 안된 값을 넣어줘야 한다. (%09값은 tab이므로 메모장 같은데서 직접 tab을 써줘야 한다. 띄어쓰기 몇번 한걸로 tab으로 인식이 안된다)

그러면은 memo페이지에 flag값이 올라와있는걸 알 수 있다.

 

몇가지 더 살펴보자면 javascript 문법 상 unicode escape sequence가 지원된다. 그래서 원래라면 유니코드로 변환된 값을 써줘도 무방하겠지만 이외의 환경에서도 동일한 방식으로 우회할 수 있는것은 아니다. 

<iframe src="javas    cript:locatio    n.href='/memo?memo='+documen\u0074.cookie">

때문에 위와 같이 코드를 작성해도 올바르게 작동하지 않는다.

(참고 : https://dreamhack.io/forum/qna/3241/)

 

Unicode escape sequence

javascript: 스키마를 사용하려 했는데 여기서 javascript는 Unicode escape sequence로 우회하면 안되는 건가요?

dreamhack.io

 

생각보다 우여곡절이 많았던 문제이다. 아무리 어려워도 여러 이론들을 구글링 해보고 차근차근 푸는게 중요한거 같다.

728x90
반응형