본문 바로가기

Web Hacking/Dreamhack

[Dreamhack] file-csp-1 write up

문제 서버를 보면 

 

Test CSP와 Verify CSP가 존재한다. CSP 헤더를 맞게 설정한 후 테스트하는 창과 검증하는 창인 것 같다.

 

#!/usr/bin/env python3
import os
import shutil
from time import sleep
from urllib.parse import quote

from flask import Flask, request, render_template, redirect, make_response
from selenium import webdriver

from flag import FLAG

APP = Flask(__name__)


@APP.route('/')
def index():
    return render_template('index.html')


@APP.route('/test', methods=['GET', 'POST'])
def test_csp():
    return render_template('test.html')


@APP.route('/verify', methods=['GET', 'POST'])
def verify_csp():
    global CSP
    if request.method == 'POST':
        csp = request.form.get('csp')
        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(f'http://localhost:8000/live?csp={quote(csp)}')
            try:
                a = driver.execute_script('return a()');
            except:
                a = 'error'
            try:
                b = driver.execute_script('return b()');
            except:
                b = 'error'
            try:
                c = driver.execute_script('return c()');
            except Exception as e:
                c = 'error'
                c = e
            try:
                d = driver.execute_script('return $(document)');
            except:
                d = 'error'

            if a == 'error' and b == 'error' and c == 'c' and d != 'error':
                return FLAG

            return f'Try again!, {a}, {b}, {c}, {d}'
        except Exception as e:
            return f'An error occured!, {e}'

    return render_template('verify.html')


@APP.route('/live', methods=['GET'])
def live_csp():
    csp = request.args.get('csp', '')
    resp = make_response(render_template('csp.html'))
    resp.headers.set('Content-Security-Policy', csp)
    return resp


if __name__ == '__main__':
    APP.run(host='0.0.0.0', port=8000, threaded=True)

 

코드를 해석해보자면, 

 

Test 폼에서 보낸 값이 /live로 전달되고 이를 검사한다. 

 

 

/live에선 인자로 받은 값으로 CSP헤더를 설정 후 HTTP 응답을 리턴한다. csp.html의 결과를 리턴하기 때문에 이를 확인해보자.

 

csp.html

 

csp.html을 보면 4개의 script 태그가 존재한다. 1개는 아무 설정도 없는 인라인 태그로 정의되어 있고, 두 개는 nonce값을 설정하고 있다. 나머지 하나는 jquery를 로드하고 있다. 이제 flag를 어디서 얻는지 확인해보자.

 

 

/verify에 /test에서 보내는 것과 같이 csp 헤더를 넘기면 admin이 해당 csp헤더를 가지고 /live에 방문한다. 이 때 csp.html에 있는 a(), b(), c(), 와 $(document)의 리턴 값이 다음과 같을 때 FLAG를 얻는다.

 

a -> error, b -> error, c = 'c', d != error

 

즉, a() 와 b()함수는 CSP 헤더에 의해 막혀야 되고, c()함수와 $(document)함수는 실행되도록 해야 된다.

 

먼저, d의 값은 jquery를 로드하는 부분이기 때문에 당연히 jquery 부분은 허용되도록 했다. 또한, c도 허용되어야 되므로 nonce값을 지정해줬다.

 

 

결과를 보면

 

a는 인라인 아무것도 지정되지 않은 인라인 태그이기 때문에 당연히 실행이 안 됐다. (CSP 헤더를 설정할 경우 인라인 태그는 디폴트로 작동이 안 된다. 허용을 해줘야 작동을 한다.)

 

b가 작동을 안해야 되는데 b가 c와 같은 nonce 값을 가지기 때문에 작동을 한 것을 볼 수 있다.

 

c가 작동을 하면서 b가 작동을 안 하도록 하는 방법을 찾아야 한다. 특정 인라인 태그만 허용하도록 해시를 설정할 수 있다. 태그를 제외한 코드를 sha256 해시화 후 base64 인코딩하여 지정해주면 된다.

 

MDN

c함수만 지정해주자.

 

 

csp헤더를 일부로 오류 코드로 작성 후 보내면 개발자 도구 콘솔 창에서 에러 메시지를 확인할 수 있다. 위 에러 메시지는 csp 헤더를 잘 못 설정했으니 다음과 같이 수정하라는 가이드를 제시한다. 에러 메시지마다 sha256-인코딩값이 존재한다. 즉, 각각의 script 태그에 대한 해시값이며, 해당 값을 설정해주면 허용이 된다.

 

c함수의 해시값은 20번째 줄이므로 맨 아래 해시값을 넣어주자.

 

 

 

끝!

 

CSP헤더에 대해 깊게 이해할 수 있었던 좋은 문제!

'Web Hacking > Dreamhack' 카테고리의 다른 글

[Dreamhack] filestorage write up  (0) 2024.04.26
[Dreamhack] crawling write up  (0) 2024.04.26
[Dreamhack] baby-sqlite write up  (0) 2024.04.22
[Dreamhack] chocoshop write up  (2) 2024.04.20
[Dreamhack] web-deserialize-python write up  (0) 2024.04.16