rsp-∞

[Dreamhack] xss-2 본문

Write-ups/web

[Dreamhack] xss-2

portrait.kim 2025. 4. 4. 16:12

서버를 생성하고 사이트에 접속하면 전체적인 인터페이스는 똑같지만 여기서 차이가 있다.

 

vuln(xss)page에서 url에 똑같이 <script> 태그를 사용하여 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**]"


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)


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


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


@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 부분에서 사용자가 입력한 param 값을 직접 출력하지 않고, 정적 html인 param.html로 렌더링한다. 그러므로 xss-1에서 보았던 reflected xss 취약점이 제거된 것을 알 수 있다. 그러나 stored xss 취약점은 여전히 존재한다. /memo 엔드포인트가 여전히 필터링 없이 데이터를 저장하고 출력하고 있기 때문이다.

 

그렇다면 <script> 태그는 사용이 불가능하고, 다른 사용 가능한 자바 스크립트 태그를 찾아보자. 구글링하면 쉽게 찾아볼 수 있는데, 대표적으로 iframe, img, object 등이 있다. 각 태그에 따라 html에 출력시키는 형식이 다른데, iframe은 html 문서 내에 또 다른 html 문서를 불러올 수 있다고 한다. XSS가 발생한 페이지와 데이터를 가져올 페이지가 다를 경우에는 <iframe>을 사용하면 된다.

 

코드가 어떻게 동작하는지는 위 차이점을 제외하면 xss-1과 유사하므로 따로 설명하지는 않겠다. <iframe> 태그를 사용하여 페이로드를 작성할 때 주의할 점은 <script> 태그와 작성 양식이 조금 다르다는 것뿐이다. 그 외의 내용은 xss-1에 보낸 페이로드와 크게 다르지 않다. 페이로드는 아래와 같이 작성하였다.

 

<iframe src="javascript:location.href='/memo?memo='+document.cookie"></iframe>

 

페이로드를 자세히 보면 <iframe> 태그를 사용하고, 그 외의 location.href와 document.cookie 속성을 xss-1과 동일하게 사용한 것을 볼 수 있다. iframe 뒤에 src가 붙는 이유와 javascript:의 기능이 궁금하였는데, 먼저 javascript:는 <iframe> 태그가 기본적으로 자바가 아닌 html에서 사용하는 태그라서, 해당 태그를 자바를 거쳐 자바 스크립트로 기능할 수 있도록 붙여 주는 일종의 사족이다. 반대로 javascript:를 사용하지 않는 <script>는 html에서 자바 스크립트를 사용할 때 쓰는 태그이다. 그러므로 굳이 javascript:를 붙여 주지 않아도 자연스럽게 자바를 거쳐 사용되는 태그인 것이다.

 

iframe 뒤에 src가 붙는 이유는 간단하다. iframe이 기본적으로 한 html 안에 다른 html을 불러 오는 기능을 하기 때문에, 어떤 웹 페이지를 불러 올지 지정해야 한다. 그 기능을 하는 것이 src 속성이다. 그러므로 위 페이로드를 해석하면 iframe 태그를 사용하여 다른 웹 페이지를 불러올 것이고, 그 과정에서 자바를 거쳐 location.href='memo?memo=' 페이지로 이동한다. 그리고 해당 페이지에서 document.cookie에 포함된 쿠키가 가지고 있는 flag를 출력시킨다.

 

다음과 같이 플래그를 획득할 수 있었다.

'Write-ups > web' 카테고리의 다른 글

[Dreamhack] csrf-2  (0) 2025.04.04
[Dreamhack] csrf-1  (0) 2025.04.04
[Dreamhack] xss-1  (0) 2025.04.04
[DreamHack] session-basic  (0) 2025.03.27
[DreamHack] cookie  (0) 2025.03.27