https://dreamhack.io/wargame/challenges/36
csp에 대해 공부해볼 수 있는 문제다
초기 페이지다. Test 페이지와 Verify 페이지로 나뉘어져 있는 것이 보인다.
#!/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)
app.py와 코드다.
test 페이지와 verify 페이지 둘 다 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>
csp.html의 내용은 위와 같다.
해석해보면 스크립트가 줄지어 있는데, a,b는 실행시키지 못하도록 막고 c,d는 실행시켜야 한다.
@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
csp에 대한 헤더를 `/live`에 의해 설정되고 있다. 그럼 이 헤더에 들어갈 값을 넣어주면 될 것이다.
a 함수의 경우, 인라인으로 스크립트가 삽입되어 있다. csp는 `script-src [허용해줄 것]` 의 형식으로 사용된다. 따라서 우리는 unsafe-inline 의 사용을 하지 않고 c와 d를 허용해주어야한다.
b 함수의 경우, nonce값으로 `i_am_super_random` 값을 가지고 있다. c와 d가 포함되어있는 스크립트도 nonce값으로 이 값을 가지고 있다. 따라서 nonce 값으로 c와 d를 허용시키는 방법은 막힌 상태이다.
그럼 우리는 unsafe-inline과 nonce값을 사용하지 않고 c와 d만을 허용시킬 방법을 찾아야한다.
여기에서 우리는 hash 값을 사용할 수 있다. 스크립트 태그 안에 내용들을 해싱하고, 그 값을 넣어주는 것이다. 스크립트 태그 안에 내용들을 해싱할 때 들여쓰기까지 신경써서 복사해주자.
위와 같이 나왔다.
https://report-uri.com/home/hash
이 사이트에서 해싱했다.
직접 해싱하지 않고도 `script-src 'none'`을 입력하고 개발자 도구의 콘솔을 살펴보면 해싱 값을 볼 수도 있다.
script-src 'sha256-l1OSKODPRVBa1/91J7WfPisrJ6WCxCRnKFzXaOkpsY4='
이런 값을 입력창에 입력해보았다.
d가 제대로 출력되지 않는다.
<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>
try/catch 구문을 살펴보면 `$(document)`가 보인다. 이것은 jquery의 문법임을 알 수 있다. jquery를 가져와야 하는데 위의 스크립트에서 가져오고 있다. 위의 스크립트에서 hash값을 가지고 있는데, 이 값을 입력해줘도 잘 작동한다.
script-src 'sha256-l1OSKODPRVBa1/91J7WfPisrJ6WCxCRnKFzXaOkpsY4=' 'sha256-pasqAKBDmFT4eHoN2ndd6lN370kFiGUFyTiUHWhU7k8='
이 방법 외에도 참조하는 저 url을 직접 넣어줘도 된다.
script-src 'sha256-l1OSKODPRVBa1/91J7WfPisrJ6WCxCRnKFzXaOkpsY4=' https://code.jquery.com/jquery-3.4.1.slim.min.js
test 페이지와 verify 페이지 둘 다 같은 csp.html을 사용하기 때문에 그대로 verify 페이지의 입력창에 써주면 flag를 얻을 수 있다.
짜잔
'웹 해킹 > 드림핵' 카테고리의 다른 글
[드림핵] crawling 풀이 (0) | 2024.02.11 |
---|---|
[드림핵] weblog-1 풀이 (0) | 2024.02.10 |
[드림핵] Dream Gallery 풀이 (0) | 2024.01.22 |
[드림핵] login-1 풀이 (1) | 2024.01.13 |
[드림핵] baby-sqlite 풀이 (2) | 2024.01.10 |