본문 바로가기
컴소니/보안

[웹 해킹] Dreamhack XSS Filtering Bypass Advanced(Level 3)

by 금소니 2023. 8. 31.
반응형

#266

1. 개요

워게임 명 : XSS Filtering Bypass Advanced

난이도 : Level 3

관련 개념 : Javascript, XSS, Cookie, HTML

문제 : XSS 취약점을 이용하여 FLAG 값 획득

* 이전에 풀이하였던 XSS Filtering Bypass 워게임에 난이도가 상승한 워게임입니다.

XSS  Filtering Bypass 강의에 포함된 워게임입니다.

2. 소스 코드 확인

1) HTML

이전의 XSS Filtering Bypass 워게임에서 사용한 소스 코드와 동일합니다.

 

1-1) base.html

1-2) index.html

1-3) memo.html

1-4) flag.html

파라미터를 입력받는 페이지입니다.

2) python

python 소스의 경우 필터링과 관련된 부분에 문자열이 추가되었습니다.

 

2-1) 초기 선언 부분

#!/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**]"

2-2) read_url 함수

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

2-3) check_xss

def check_xss(param, cookie={"name": "name", "value": "value"}):
    #check_xss는 read_url함수 호출하여 vuln 엔드포인트 접속
    url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"
    return read_url(url, cookie)

2-4) xss_filter

#xss 취약점에 대해 필터링을 위한 문자열
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

2-5) app.route("/")

#render_template : flask에서 제공하는 함수로 templates에 저장된 html을 불러올 때 사용하는 함수
@app.route("/")
def index():
    return render_template("index.html")

2-6) app.route("/vuln")

#사용자가 입려갛ㄴ param 값을 출력
@app.route("/vuln")
def vuln():
    param = request.args.get("param", "")
    #필터링과 관련된 함수 호출
    param = xss_filter(param)
    return param

2-7) app.route("/flag", methods["GET", "POST"])

@app.route("/flag", methods=["GET", "POST"])
def flag():
    #이용자에게 URL을 입력받는 페이지를 제공
    if request.method == "GET":
        return render_template("flag.html")
    elif request.method == "POST":
        param = request.form.get("param")
        #파라미터 값과 쿠키에 FLAG를 포함해 check_xss 함수 호출
        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>'

2-8) app.route("/memo")

memo_text = ""

#사용자가 요청한 내용을 메모로 작성하여 출력
#여기는 render_template를 통해 출력하기 때문에 취약하지 않음
@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)

2-8) 서비스 실행

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

3. 웹 화면 확인

XSS Filtering Bypass와 동일한 웹 페이지 구성입니다.

 

1) /vuln

취약한 페이지로 드림핵 홈페이지의 로고를 호출하도록 작성한것 같은데 제대로 로드가 되지 않았습니다.

마찬가지로 변경해서 요청해봤습니다.

2) /memo

여기는 마찬가지로 메모가 입력되어 저장되는 경로로 링크를 클릭할 때 마다 hello 문구가 추가됩니다.

3) /flag

이 곳이 저희가 실제 테스트하여 flag 값을 얻을 경로입니다.

4.  문제 풀이

1) 요구사항 파악

XSS Filtering Bypass 워게임에 필터링 문자열과 필터링 시 치환되는 문구가 변경되었습니다.

따라서 해당 문자열을 우회하는 방법을 통해 flag 값을 획등해야 합니다.

필터링 되는 문자열들을 잘 인지하고 있어야 합니다.

(script, on, javascript, window, self, this, document, loaction, (, ), &#)

 

2) Tab(%09), 개행(%0D) 아스키 코드를 이용하여 스크립트 실행

먼저 이번 워게임을 풀기 위해서는 아스키 코드표를 참조해야 합니다.

저희가 명령을 실행할 때 Tab이나 혹은 개행이 되더라도 해당 명령어를 실행할 수 있습니다.

따라서 아스키코드에서 사용되는 Tab 값과 개행 값을 이용하였습니다.

<출처 : asciitable.com>

3) Tab(%09)을 이용하여 alert 띄우기

가장 먼저 취약한 페이지인 /vuln 페이지에 Tab을 이용하여 문자열을 우회, 스크립트 실행을 해봤습니다.

약간의 힌트를 얻어 iframe 소스에 삽입하는 걸로 작성해봤습니다.

하지만 참고해야할 것은 괄호도 필터링되어 있어서 이를 우회하기 위해 이중 인코딩 방법을 이용하였습니다.

(괄호의 인코딩 값 : %28("("), %29(")"))

 

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

 

위와 같이 입력하였을 때 아래오 같이 alert가 뜨는 것을 확인하였습니다.

이를 통해 Tab을 이용한 우회로 공격이 가능해보였습니다.

 

4) 스크립트 짜보기

그럼 위 내용을 바탕으로 flag 값을 얻기위한 스크립트를 작성해보도록 하겠습니다.

 

<iframe src="javas%09cript:locatio%09n.href='/memo?memo='+do%09cument.cookie">

 

작성한 스크립트를 /flag 페이지에 입력하였지만 /memo 페이지에 flag 값을 얻을 수 없었습니다.

원인이 무엇인지 한참을 헤매다가 찾게 되었습니다.

 

/vuln 페이지를 통해 파라미터를 입력하는 것과 /flag 페이지를 통해 파라미터를 입력하여 동작하는 것은 인코딩에 차이가 있습니다.

/flag 페이지의 경우 urllib.parse.quote 함수를 통하여 인코딩된 후에 동작하기 때문에 인코딩되지 않은 값을 입력해야하는 것이였습니다.

즉, 제가 작성한 스크립트가 100% 인코딩된 건 아니지만 인코딩된 값이 있어 이를 디코딩하여 입력해야 flag 값을 얻을 수 있었습니다.

 

<iframe src="javas cript:locatio n.href='/memo?memo='+do cument.cookie">

 

즉, 제가 작성한 스크립트가 100% 인코딩된 건 아니지만 인코딩된 값이 있어 이를 디코딩하여 입력해야 flag 값을 얻을 수 있었습니다.

Level 3의 난이도여서 그런지 역시 쉽지는 않았습니다.

반응형

댓글