[WARGAME] 드림핵 워게임- CSP-Bypass

jckim22·2022년 11월 4일
0

[WEBHACKING] STUDY (WARGAME)

목록 보기
47/114

먼저 아래 코드를 보자

#!/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, 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.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}'"
    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 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")
        if not check_xss(param, {"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 엔드포인트를 보면 그냥 xss 공격에 완전히 노출되어 있다.

먼저 일반적인 xss 공격을 해보자 우회 조차도 필요없다
아래처럼 우리가 이용할 vuln페이지의 param에 스크립트 코드를 삽입했다.

하지만 아래와 같은 결과가 나왔다.
CSP때문에 일반적인 스크립트 공격이 가능하지 못했다.
nonce의 난수를 보면 매번 다르게 생성되기 대문에 예측 조차 불가능했다.

그래서 header에 csp를 추가해주는 함수 부분을 분석해보았다.
아래를 보자

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}'"
    nonce = os.urandom(16).hex()
    return response

동일한 오리진의 출처는 허용해주는 것을 볼 수 있다.

그렇다면 vuln의 param의 내용을 html로 출력해주는 것을 이용하지말고 script src에서 같은 출처인 vuln페이지를 한번 더 불러오는 방법으로 공격할 수 있을 것이라고 생각했다.

불러오는 페이지의 param 자체를 스크립트 코드로 한다면 불러오면서 출처가 같기 때문에 csp에 걸리지 않게 될 것이다.
그렇게 되면 script 코드가 불러질 때 자동적으로 실행될 것이다.

그렇게 생각해낸 코드는 아래와 같다.

<script src="/vuln?param=location.href='/memo?memo='+document.cookie"></script>

하지만 이 코드로는 성공하지 못했다.

그 이유는 바로 script부분이 param으로 두번 이나 인식되기 때문이었다.

처음에 한번 src에서 한번인 것이다.

그렇게 되면 +는 url 디코딩이 될것이다,
+를 url디코딩 하면 공백이 된다.
그렇기에 미리 한번 인코딩을 해놔야 src로 불러올 때 디코딩이 되어 +가 되는 것이다.

그래서 +를 인코딩 해보니 %2b가 나왔다.

그럼 최종적으로 아래 코드가 나오게 된다.

<script src="/vuln?param=location.href='/memo?memo='%2bdocument.cookie"></script>

이 코드를 입력하게 되면

임의의 사용자는 이 코드가 작동되는 url에 접속하게 되고 그 url에서는 먼저 동일한 출처의 /vuln페이지를 불러온다.
그 불러온 페이지의 param은 memo 페이지로 가서 사용자의 쿠키를 메모하라는 내용이 될 것이다.

그렇게하면 아래처럼 flag를 얻을 수 있다.

profile
개발/보안

0개의 댓글