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)
POST를 날리면 그 값을 /live?csp={quote(csp)} 로 묶어서 던지게 된다.
a == 'error' b == 'error' c == 'c' d != 'error'를 만족해야 한다.
그래야 return FLAG를 해준다.
저 아래의 live_csp를 보면 csp를 인자로 받는다. 그리고 우리가 설정한 csp를 헤더로 설정해준다.
<!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만 만족시키면 된다.
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='
이렇게 보내주면 된다.
자잔