← 전체 Write-Up

Web / Dreamhack

XSS-2

Dreamhack 문제의 풀이와 실습 과정을 원문 그대로 담은 개인 기록입니다.

읽기 안내

Dreamhack의 개인 문제 풀이 기록입니다. 원문에 남아 있는 실습 주소·플래그와 실패·미완료 결과를 그대로 보존했습니다.

XSS-2


웹 서비스 분석

엔드포인트: /vuln

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

XSS-1과 XSS-2의 문제가 차이가 하나 있다.** get**으로 유저가 입력한 파라미터 값을 바로 return 하는 형태가 아니라, render_template 함수를 사용하고 있다는 점이다. render_template 함수는 Flask 웹 프레임워크에서 제공하는 함수다. 주어진 템플릿 파일의 이름과 함께 전달된 변수나 값들을 템플릿에 적용하여 완성된 HTML을 생성한다. render_template 함수를 사용하면, 전달된 템플릿 변수가 기록될 때 HTML 엔티티코드로 변환해 저장되기 때문에 XSS가 발생하지 않게 된다. 즉, 사용자가 입력한 값을 페이지에 그대로 출력하지 않는 것이다. 그래서 XSS-2의 vuln 페이지에서는 파라미터 값으로 <script>alert(1)</script>를 입력하더라도 XSS가 발생하지 않는다.

엔드포인트: /memo

@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)

memo 페이지는 XSS-1와 동일하다. 유저가 전달한 memo 파라미터 값을 render_template 함수를 통해 기록하고 출력한다. 유저가 URL의 memo 파라미터에 어떤 값을 입력하는지에 따라 화면에 출력하는 값도 달라진다.

엔드포인트: /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("/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>'

check_xss 함수에서는 유저가 flag에 POST로 전송한 파라미터 값이 XSS 공격에 사용될 수 있는 값인지 아닌지를 확인한다. 이때 check_xss에서는, 유저가 flag 페이지에 입력한 파라미터 값을 포함하여 vuln 페이지에 접근하는 URL을 생성하게 된다. 결과적으로 read_url 함수를 통해 vuln 페이지에 접근하는 URL과 사용자의 쿠키가 전달되는 것이다.

취약점 분석


XSS-1과는 다르게, vuln 페이지에 존재하는 innerHTML을 통해 <script> 태그로 XSS를 발생시킬 수 없는 상황이다. 따라서 다른 XSS 공격 벡터를 시도해봐야 한다. 이때 innerHTML은 웹 개발에서 사용하는 DOM(Document Object Model)의 속성 중 하나다. HTML 문서의 구조나 내용을 프로그래밍적으로 조작하고자 할 때 Javascript를 통해 DOM을 사용한다. innerHTML 관련 문서는 여기로...

innerHTML이란?
자바스크립트에서 innerHTML은 HTML 요소의 내용을 가져오거나 변경할 때 사용하는 속성이다.

코드 설명
var content = document.getElementById('example').innerHTML; 읽기: 요소의 내부 HTML을 가져옵니다.
document.getElementById('example').innerHTML = 'New Content'; 쓰기: 요소의 내부 HTML을 변경합니다.

innerHTML을 사용하면 요소의 내부 HTML을 문자열로 읽거나 설정할 수 있다. 이렇게 유저 입력을 바탕으로 innerHTML을 설정했을 때 XSS 공격에 취약할 수 있으므로 주의가 필요하다.

vuln.html

<script>var x=new URLSearchParams(location.search); document.getElementById('vuln').innerHTML = x.get('param');</script>

현재 페이지의 URL 쿼리 문자열에서 파라미터 값을 추출하고, 해당 값을 페이지 내의 vuln이라는 ID를 가진 요소의 내부 HTML로 설정하는 코드다. 즉, 파라미터의 값을 통해 '쓰기'를 수행하는 것이다. vuln.html에 존재하는 위 코드는, innerHTML을 통해 사용자가 URL의 param 쿼리 파라미터 값을 조작하여 웹 페이지의 내용을 변경할 수 있게 한다는 점에서 취약할 수 있다.

URL 쿼리 파라미터?
웹 페이지 URL의 쿼리 문자열(예: ?key=value)을 통해 서버나 클라이언트에 데이터를 전달할 수 있다.
예를 들어, example.com?page=1 에서 page=1이 쿼리 파라미터다.

익스플로잇


이제 유저의 쿠키 값을 탈취해 문제를 풀어보자. <img src> 태그를 사용하여 다음과 같은 익스플로잇 코드를 작성할 수 있다.

<img src="XSS-2" onerror="location.href='/memo?memo='+document.cookie">

이 코드를 /flag 페이지의 param 부분에 입력하면, memo에서 유저가 쿠키 정보를 확인할 수 있게 된다. 이때 /vuln 페이지의 param에 해당 익스플로잇 코드를 입력하면 이미지 로딩에 실패한 화면이 보여진다.

왜 <script> 태그가 아닌 <img src> 태그를 사용할까??

XSS 공격에서는 악의적인 스크립트를 주입하여 피해자의 브라우저에서 해당 스크립트가 실행되도록 한다. 그러나, 일부 웹 앱에서는 보안 조치를 통해 <script> 태그를 필터링하거나 제거하는 경우가 많다.
이때 <img src> 태그는 대체로 필터링되지 않으며, 이미지 경로에 악성 스크립트를 삽입하는 방식으로 스크립트 오류를 발생시키는 것이다. 이미지의 경로가 잘못되었을 때 브라우저가 이를 처리하면서 오류를 발생시키고, 그 과정에서 onerror 이벤트 핸들러를 사용해 Javascript를 실행할 수 있는 것이 그 이유다.

후기


이 문제에서는 XSS-2 문제를 통해 쿼리 파라미터를 조작해 웹 페이지의 내용을 변경할 수 있게 하는 취약점이 발생시킬 수 있는 문제를 알아봤다. 이 문제처럼 유저 입력을 바탕으로 innerHTML을 설정했을 때 XSS 공격에 취약할 수 있으므로, 사용자 입력을 받기 전에 서버 측에서 입력값을 검증하거나 escape 처리를 하는 방식 등을 사용하는 것이 좋다.

작성 기록과 문제 출처

원문과 출처

풀이와 설명: MadEyE / 이선용의 개인 Notion 기록.
문제에서 제공한 코드·서비스 화면과 외부 참고자료의 권리는 해당 작성자와 플랫폼에 있습니다.

원문 본문·코드·실습 증적을 보존했으며, 원문에 포함된 외부 글은 참고 링크로 안내합니다.