[Dreamhack] file-csp-1

김성진·2022년 11월 27일
0

📒 Description

CSP를 작성하라고 한다.


📒 Analysis & Exploit

📖 app.py

#!/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)

POST를 날리면 그 값을 /live?csp={quote(csp)} 로 묶어서 던지게 된다.

a == 'error' b == 'error' c == 'c' d != 'error'를 만족해야 한다.
그래야 return FLAG를 해준다.

저 아래의 live_csp를 보면 csp를 인자로 받는다. 그리고 우리가 설정한 csp를 헤더로 설정해준다.

📖 csp.html

<!doctype html>
<html>
	<head>
	<!-- block me -->
	<script>
		function a() { return 'a'; }
		document.write('a: block me!<br>');
	</script>
	<!-- block me -->
	 <script nonce="i_am_super_random">
		function b() { return 'b'; }
		document.write('b: block me!<br>');
	</script>
	<!-- allow me -->
	<script
  src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
  integrity="sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8="
  crossorigin="anonymous"></script>
	<!-- allow me -->
	 <script nonce="i_am_super_random">
		function c() { return 'c'; }
		document.write('c: allow me!<br>');
		try { $(document); document.write('jquery: allow me!<br>'); } catch (e) {  }
	</script>
	</head>
</html>

https://content-security-policy.com/
사실 위 글을 보고도 이해가 잘 가지 못했다. 그냥 정리해놓은 느낌..
https://velog.io/@dung002/CSP%EB%9E%80
이 링크를 보고 a를 보자. none 값을 주면 모든 곳에서 걸리게 되는데 확인해보자.


유심히 보면 첫 번째 sha 저 값은 a 함수일테고, 그렇게 b http c로 매칭시킬 수 있다.
특히 http의 경우 jquery도 있기에 확실히 확인된다.

근데 생각해보면 우리는 c랑 d만 만족시키면 된다.

📖 Exploit

http와 d를 어떻게 허용시킬 수 있을까.

http를 먼저 확인해보자.
https://code.jquery.com/jquery-3.4.1.slim.min.js
이것이 해당 주소이므로

script-src https://code.jquery.com/jquery-3.4.1.slim.min.js

이제 c를 보자. c는 sha256-l1OSKODPRVBa1/91J7WfPisrJ6WCxCRnKFzXaOkpsY4=
이렇게 존재하는데, 이 또한 우회해줄 수 있다.

script-src 'sha256-l1OSKODPRVBa1/91J7WfPisrJ6WCxCRnKFzXaOkpsY4='

사실 여기서 많이 실수한 점이 있는데 저 따옴표를 묶어주어야 한다.

저걸 합치면

script-src https://code.jquery.com/jquery-3.4.1.slim.min.js 'sha256-l1OSKODPRVBa1/91J7WfPisrJ6WCxCRnKFzXaOkpsY4='

이렇게 보내주면 된다.
자잔

profile
Today I Learned

0개의 댓글