본문 바로가기

카테고리 없음

CSP Bypass - dreamhack

문제를 풀이하기위해서는 제공되는 코드를 사전에 확인해줄 필요가 있다.

 

- /vuln

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

vuln 페이지를 구성하는 코드는 위와 같다. 이용자가 전달한 파마리터 값을 출력한다.

 

- /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, nonce=nonce)

memo 페이지를 구성하는 코드다. 이용자가 전달한 memo 파라미터의 값을 render_template함수를 통하여 기록,출력한다.

 

- /flag

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

flag 페이지를 구성하는 코드다. 메소드에 따른 요청마다 다른 기능을 수행한다.

GET 의 경우 이용자에게 URL을 입력받은 페이지를 제공한다.

POST의 경우 파라미터에 값과 쿠키에 FLAG를 포함하여 check_xss함수를 호출한다.

check_xss는 read_url을 호출하여 vuln에 접속한다.

 

- /add_header

@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

http응답에 CSP 헤더를 추가하는 코드이다. 해당 코드에서 살펴야 할 부분은

어떤 방식으로 CSP 정책이 정의되있는가이다.

CSP 정책 중 script와 관련된 정책을 살펴보면 nonce속성을 필요로하며

같은 출처 내의 파일만을 script의 src로 올 수 있도록 출처를 제한하고 있다.

공격자는 nonce값을 알 수 없으므로 XSS취약점이 존재해도 일반적인 방법으로는 공격을 수행할 수 없다.

 

 

memo에서 render_template함수를 이용하여 memo.hmtl을 출력한다.

render_template함수는 전달된 템플릿 변수를 기록할 때 HTML 엔티티 코드로 변환해 저장하기 때문에

XSS 취약점이 발생하지 않는다. 그러나 vuln은 이용자가 입력한 값을 페이지에 그대로 출력하므로

XSS 취약점이 발생할 수 있다.

CSP정책이 적용되어 있어 곧바로 스크립트를 실행하기는 어렵다. 정책을 살펴보면 허용되는 출처는 self다.

nonce를 예측하는 것은 불가능하므로 같은 출처 내에서 파일을 업로드하거나

원하는 내용을 반환하는 페이지가 존재할 경우 이를 이용하여 공격할 수 있다.

 

문제 해결을 위해서 /vuln에서 발생하는 XSS 취약점을 통해 임의 이용자의 쿠키를 탈취한다.

XSS 공격이 정상적으로 통하는 것을 확인하였으면 다음과 같이 입력한다.

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

위와 같이 입력하면 memo페이지에서 정답을 확인할 수 있다.

주의할 점을 두번에 걸쳐 스크립트 부분을 파라미터로 해석하므로 URL decoding되어 공백으로 해석되지 않도록

+대신 %2b를 이용하여 공백을 처리한다.