[๐Ÿฅ‰ ๋ธŒ๋ก ์ฆˆ 2] ์‘๋‹ต ํฌ๊ธฐ๋กœ ๋˜๊ฐ์€ blind SQLi โ€” DreamHack access-log ํ’€์ด

2026-07-13ยท1๋ถ„ ์ฝ๊ธฐยท

[๐Ÿฅ‰ ๋ธŒ๋ก ์ฆˆ 2] ์‘๋‹ต ํฌ๊ธฐ๋กœ ๋˜๊ฐ์€ blind SQLi โ€” DreamHack access-log ํ’€์ด

access.log ํ•˜๋‚˜๋งŒ ์ฃผ์–ด์ง„ ํฌ๋ Œ์‹ ๋ฌธ์ œ. ๋กœ๊ทธ๋Š” sqlmap์ด DVWA์— ๋˜์ง„ time-based blind SQLi๋กœ ๊ฐ€๋“ํ•˜๋‹ค. time-based์˜ ์›๋ž˜ ์‹ ํ˜ธ์ธ '์ง€์—ฐ'์€ nginx ๊ธฐ๋ณธ ๋กœ๊ทธ์— ์—†์ง€๋งŒ, ์ฃผ์ž…๋œ IF๊ฐ€ WHERE์˜ ํ–‰ ์œ ๋ฌด๋ฅผ ๋ฐ”๊พธ๋Š” ๋•์— ์‘๋‹ต ๋ฐ”์ดํŠธ(1664 = ์กฐ๊ฑด ์ฐธ)๊ฐ€ ์˜ค๋ผํด์ด ๋œ๋‹ค. 4977๊ฐœ ์ถ”์ถœ ์ฟผ๋ฆฌ์˜ ORD(MID()) ๋น„๊ต๋ฅผ ๋˜๊ฐ์•„ dvwa.flag.value = DH{...}๋ฅผ ๋ณต์›ํ–ˆ๋‹ค.

๋ฌธ์ œ: DreamHack โ€” access-log ๋ถ„๋ฅ˜: Forensics ๋‚œ์ด๋„: ๐Ÿฅ‰ ๋ธŒ๋ก ์ฆˆ 2 FLAG: DH{anA1yz1nGVe3yB19L0g}

๋ฌธ์ œ ์„ค๋ช…์€ ๋‹ด๋ฐฑํ•˜๋‹ค.

๋‹น์‹ ์€ ์ด๋ฒˆ ํ•ดํ‚น ์‚ฌ๊ฑด์˜ ๋ถ„์„์„ ๋‹ด๋‹นํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ access.log ํŒŒ์ผ ํ•˜๋‚˜๋งŒ์ด ์ œ๊ณต ๊ฐ€๋Šฅํ•˜๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๊ณผ์—ฐ ๋ถ„์„์„ ๋งˆ์น˜๊ณ  ํ”Œ๋ž˜๊ทธ๋ฅผ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ์„๊นŒ์š”?

์ฆ‰ ๊ณต๊ฒฉ์ž๊ฐ€ ์„œ๋ฒ„์— ๋ฌด์Šจ ์ง“์„ ํ–ˆ๋Š”์ง€ ๋กœ๊ทธ๋งŒ ๋ณด๊ณ  ๋˜์งš์–ด์•ผ ํ•œ๋‹ค. ๋กœ๊ทธ๋ฅผ ์—ด์–ด ๋ณด๋ฉด ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ sqlmap์˜ SQL ์ธ์ ์…˜ ํ”์ ์ด๋‹ค. ๊ณต๊ฒฉ์ž๊ฐ€ blind SQLi๋กœ ๋ฌด์–ธ๊ฐ€๋ฅผ ํ•œ ๊ธ€์ž์”ฉ ๋นผ๋‚ด ๊ฐ”๊ณ , ๊ทธ "๋ฌด์–ธ๊ฐ€"๊ฐ€ ํ”Œ๋ž˜๊ทธ๋‹ค. ๋กœ๊ทธ๋ฅผ ๊ฑฐ๊พธ๋กœ ๋Œ๋ ค ๊ณต๊ฒฉ์ž๊ฐ€ ์–ป์–ด ๋‚ธ ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์žฌ๊ตฌ์„ฑํ•˜๋Š” ๊ฒŒ ๋ชฉํ‘œ๋‹ค.

access.log ์—ญ์ถ”์  โ€” sqlmap์˜ time-based blind SQLi๋ฅผ ๋˜๊ฐ์•„ flag ๋ณต์›
access.log ์—ญ์ถ”์  โ€” sqlmap์˜ time-based blind SQLi๋ฅผ ๋˜๊ฐ์•„ flag ๋ณต์›

๋ฌธ์ œ ๊ฐœ์š”

ํ•ญ๋ชฉ๋‚ด์šฉ
๋ฌธ์ œ๋ช…access-log
๋‚œ์ด๋„๐Ÿฅ‰ ๋ธŒ๋ก ์ฆˆ 2
๋ถ„๋ฅ˜Forensics
์ œ๊ณต ํŒŒ์ผaccess.log (nginx ์ ‘๊ทผ ๋กœ๊ทธ 6655์ค„)
๋ชฉํ‘œ๋กœ๊ทธ ๋ถ„์„์œผ๋กœ ๊ณต๊ฒฉ์ž๊ฐ€ ๋นผ๋‚ธ ํ”Œ๋ž˜๊ทธ ๋ณต์›
ํ•ต์‹ฌ ๊ธฐ๋ฒ•time-based blind SQLi๋ฅผ ์‘๋‹ต ํฌ๊ธฐ ์˜ค๋ผํด๋กœ ์—ญ์žฌ๊ตฌ์„ฑ

์ •๋‹ต ํ”Œ๋ž˜๊ทธ๋Š” ์„œ๋ฒ„ ์–ด๋”˜๊ฐ€์˜ DB์— ์žˆ์—ˆ๊ณ , ๊ณต๊ฒฉ์ž๊ฐ€ ๊ทธ๊ฑธ blind SQLi๋กœ ์ด๋ฏธ ๋นผ ๊ฐ”๋‹ค. ์šฐ๋ฆฌ๋Š” ๊ทธ ๊ณผ์ •์„ ๋กœ๊ทธ๋กœ๋งŒ ์ง€์ผœ๋ณธ ์…ˆ์ด๋ผ, ๊ณต๊ฒฉ์ž๊ฐ€ ์–ป์€ ๋‹ต์„ ๋˜‘๊ฐ™์ด ๊ณ„์‚ฐํ•ด ๋‚ด๋ฉด ๋œ๋‹ค.



๋ฐฐ๊ฒฝ โ€” DVWA, sqlmap, ๊ทธ๋ฆฌ๊ณ  time-based blind SQLi

๋กœ๊ทธ์˜ ์š”์ฒญ ๊ฒฝ๋กœ๋Š” /vulnerabilities/sqli/, User-Agent๋Š” sqlmap/1.2.4๋‹ค. ๋‘˜ ๋‹ค ์ต์ˆ™ํ•œ ์ด๋ฆ„์ด๋‹ค.

  • DVWA(Damn Vulnerable Web Application)๋Š” ์›น ์ทจ์•ฝ์  ์‹ค์Šต์šฉ ์•ฑ์ด๋‹ค. /vulnerabilities/sqli/๋Š” id ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ์ฟผ๋ฆฌ์— ๋„ฃ๋Š” ์ „ํ˜•์ ์ธ SQL ์ธ์ ์…˜ ์ง€์ ์ด๋‹ค.
  • sqlmap์€ SQL ์ธ์ ์…˜ ์ž๋™ํ™” ๋„๊ตฌ๋‹ค. ์ทจ์•ฝ์ ์„ ์ฐพ์œผ๋ฉด DB ๊ตฌ์กฐ๋ถ€ํ„ฐ ํ…Œ์ด๋ธ”ยท์ปฌ๋Ÿผยท๋ฐ์ดํ„ฐ๊นŒ์ง€ ์ž๋™์œผ๋กœ ๋นผ๋‚ธ๋‹ค.
  • blind SQLi๋Š” ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๊ฐ€ ํ™”๋ฉด์— ์ง์ ‘ ์•ˆ ๋ณด์ผ ๋•Œ, ์ฐธ/๊ฑฐ์ง“๋งŒ ์•Œ ์ˆ˜ ์žˆ๋Š” ์‹ ํ˜ธ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋น„ํŠธ์”ฉ ์บ๋‚ด๋Š” ๊ธฐ๋ฒ•์ด๋‹ค. ๊ทธ์ค‘ time-based๋Š” "์กฐ๊ฑด์ด ์ฐธ์ด๋ฉด SLEEP()์œผ๋กœ ์‘๋‹ต์„ ์ง€์—ฐ์‹œ์ผœ๋ผ"๋ผ๊ณ  ์ฃผ์ž…ํ•ด, ์‘๋‹ต์ด ๋Šฆ๋Š”์ง€ ์•„๋‹Œ์ง€๋กœ ์ฐธ/๊ฑฐ์ง“์„ ์ฝ๋Š”๋‹ค.

sqlmap์˜ time-based ํŽ˜์ด๋กœ๋“œ๋Š” ๋Œ€๋žต ์ด๋ ‡๊ฒŒ ์ƒ๊ฒผ๋‹ค.

id=1' AND 9329=IF(
  (ORD(MID((SELECT `value` FROM dvwa.flag ORDER BY id LIMIT 0,1), 1, 1)) > 64),
  SLEEP(1), 9329
)-- -

dvwa.flag ํ…Œ์ด๋ธ” value์˜ ์ฒซ ๊ธ€์ž ์•„์Šคํ‚ค ์ฝ”๋“œ๊ฐ€ 64๋ณด๋‹ค ํฌ๋ฉด SLEEP(1)๋กœ 1์ดˆ ์‰ฌ๊ณ , ์•„๋‹ˆ๋ฉด ์•ˆ ์‰ฐ๋‹ค. ์ด๋ ‡๊ฒŒ > N์˜ N์„ ์ด์ง„ํƒ์ƒ‰์œผ๋กœ ๋ฐ”๊ฟ” ๊ฐ€๋ฉฐ ํ•œ ๊ธ€์ž์˜ ์•„์Šคํ‚ค ๊ฐ’์„ ์ขํ˜€ ๋‚˜๊ฐ„๋‹ค. ๊ณต๊ฒฉ์ž๋Š” ์ด ์ง“์„ ๋ฌธ์ž ํ•˜๋‚˜ํ•˜๋‚˜, ์ปฌ๋Ÿผ ํ•˜๋‚˜ํ•˜๋‚˜ ๋ฐ˜๋ณตํ•ด DB๋ฅผ ํ†ต์งธ๋กœ ๋–  ๊ฐ”๊ณ , ๊ทธ ์š”์ฒญ์ด ์ „๋ถ€ ๋กœ๊ทธ์— ๋‚จ์•˜๋‹ค.


๐Ÿ”ฌ ์ •์ฐฐ โ€” ๋กœ๊ทธ๋Š” ์ „๋ถ€ sqlmap

๋จผ์ € ๋กœ๊ทธ์˜ ๊ทœ๋ชจ์™€ ์„ฑ๊ฒฉ์„ ๋ณธ๋‹ค.

echo "์ด $(wc -l < access.log) ์ค„"
grep -oE "sqlmap/[0-9.]+" access.log | sort | uniq -c   # User-Agent ๋ถ„ํฌ
head -1 access.log                                       # ์ฒซ ์š”์ฒญ
access.log ๊ฐœ์š” โ€” 6655์ค„ ์ค‘ 6649์ค„์ด sqlmap/1.2.4 UA, ์ฒซ ์š”์ฒญ์€ DVWA sqli ํŽ˜์ด์ง€ GET
access.log ๊ฐœ์š” โ€” 6655์ค„ ์ค‘ 6649์ค„์ด sqlmap/1.2.4 UA, ์ฒซ ์š”์ฒญ์€ DVWA sqli ํŽ˜์ด์ง€ GET

6655์ค„ ์ค‘ 6649์ค„์ด sqlmap/1.2.4๋‹ค. ์‚ฌ๋žŒ์ด ์•„๋‹ˆ๋ผ ๋„๊ตฌ๊ฐ€ ์ˆ˜์ฒœ ๋ฒˆ ์š”์ฒญ์„ ๋•Œ๋ฆฐ ํ”์ ์ด๋‹ค. ์š”์ฒญ ํ•˜๋‚˜๋ฅผ URL ๋””์ฝ”๋“œํ•ด์„œ ๋ฌด์Šจ ์ฟผ๋ฆฌ์ธ์ง€ ๋œฏ์–ด๋ดค๋‹ค.

# decode_one.py โ€” ๋กœ๊ทธ ํ•œ ์ค„์„ URL ๋””์ฝ”๋“œํ•ด blind ์ฟผ๋ฆฌ ๊ตฌ์กฐ ๋ณด๊ธฐ
import urllib.parse
for ln in open("access.log"):
    q = urllib.parse.unquote(ln)
    if "value` AS CHAR" in q and "FROM dvwa.flag" in q and ">64)" in q:
        print(q[q.find("id="):q.find(" HTTP")])
        break
URL ๋””์ฝ”๋“œํ•œ blind ์ฟผ๋ฆฌ โ€” dvwa.flag.value 1๋ฒˆ์งธ ๊ธ€์ž ORD๊ฐ€ 64๋ณด๋‹ค ํฌ๋ฉด SLEEP, ์‘๋‹ต 1664๋ฐ”์ดํŠธ๋Š” ์กฐ๊ฑด ์ฐธ
URL ๋””์ฝ”๋“œํ•œ blind ์ฟผ๋ฆฌ โ€” dvwa.flag.value 1๋ฒˆ์งธ ๊ธ€์ž ORD๊ฐ€ 64๋ณด๋‹ค ํฌ๋ฉด SLEEP, ์‘๋‹ต 1664๋ฐ”์ดํŠธ๋Š” ์กฐ๊ฑด ์ฐธ

๋””์ฝ”๋“œํ•˜๋ฉด ์•ž์˜ ๋ฐฐ๊ฒฝ์—์„œ ๋ณธ ๊ทธ ํŽ˜์ด๋กœ๋“œ๋‹ค. SELECT \value` FROM dvwa.flag์˜ ์ฒซ ๊ธ€์ž๋ฅผ ORD(MID(...)) > 64๋กœ ๋– ๋ณด๊ณ  ์žˆ๋‹ค. ๊ณต๊ฒฉ ๋Œ€์ƒ์ด dvwa.flagํ…Œ์ด๋ธ”์˜value` ์ปฌ๋Ÿผ์ž„์ด ์—ฌ๊ธฐ์„œ ๋“œ๋Ÿฌ๋‚œ๋‹ค.


๐Ÿ’ฃ ํ•ต์‹ฌ โ€” time-based์ธ๋ฐ ๋กœ๊ทธ์— ์‹œ๊ฐ„์ด ์—†๋‹ค

๋ฌธ์ œ๊ฐ€ ํ•˜๋‚˜ ์žˆ๋‹ค. time-based blind์˜ ์›๋ž˜ ์˜ค๋ผํด์€ ์‘๋‹ต ์ง€์—ฐ ์‹œ๊ฐ„์ธ๋ฐ, ์ด ๋กœ๊ทธ์—” ์‹œ๊ฐ„ ์ •๋ณด๊ฐ€ ์—†๋‹ค.

# timing.py โ€” ๋กœ๊ทธ ํฌ๋งท์— ์‘๋‹ต์‹œ๊ฐ„ ํ•„๋“œ๊ฐ€ ์žˆ๋‚˜?
line = open("access.log").readline().rstrip()
print(line)
# 172.20.0.1 - - [26/Apr/2024:...] "GET ... HTTP/1.1" 200 1686 "-" "sqlmap/..."
nginx combined ๋กœ๊ทธ ํฌ๋งท์—” $request_time ๊ฐ™์€ ์‘๋‹ต์‹œ๊ฐ„ ํ•„๋“œ๊ฐ€ ์—†์–ด SLEEP ์ง€์—ฐ์„ ์ง์ ‘ ๋ณผ ์ˆ˜ ์—†๋‹ค
nginx combined ๋กœ๊ทธ ํฌ๋งท์—” $request_time ๊ฐ™์€ ์‘๋‹ต์‹œ๊ฐ„ ํ•„๋“œ๊ฐ€ ์—†์–ด SLEEP ์ง€์—ฐ์„ ์ง์ ‘ ๋ณผ ์ˆ˜ ์—†๋‹ค

nginx ๊ธฐ๋ณธ combined ํฌ๋งท์€ IP - user [์‹œ๊ฐ] "์š”์ฒญ" ์ƒํƒœ ์‘๋‹ต๋ฐ”์ดํŠธ "referer" "UA"๋‹ค. $request_time ๊ฐ™์€ ์†Œ์š” ์‹œ๊ฐ„ ํ•„๋“œ๊ฐ€ ์—†์–ด์„œ, SLEEP์œผ๋กœ ์‹ค์ œ๋กœ ์–ผ๋งˆ๋‚˜ ์ง€์—ฐ๋๋Š”์ง€๋Š” ๋กœ๊ทธ๋งŒ ๋ด์„  ์•Œ ์ˆ˜ ์—†๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์—ฌ๊ธฐ ์‚ฌ์†Œํ•˜์ง€๋งŒ ๊ฒฐ์ •์ ์ธ ํ‹ˆ์ด ์žˆ๋‹ค. sqlmap์˜ ํŽ˜์ด๋กœ๋“œ๋Š” AND X=IF(์กฐ๊ฑด, SLEEP(1), X) ๊ผด์ด๋‹ค. ์ด๊ฑธ ๊ทธ๋Œ€๋กœ ์›๋ž˜ ์ฟผ๋ฆฌ์— ๋ถ™์ด๋ฉด WHERE user_id='1' AND X=IF(...)๊ฐ€ ๋œ๋‹ค.

  • ์กฐ๊ฑด์ด ์ฐธ์ด๋ฉด IF๊ฐ€ SLEEP(1)์„ ์‹คํ–‰ํ•˜๊ณ  ๊ทธ ๋ฐ˜ํ™˜๊ฐ’์€ 0์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด X=0์ด ๋˜์–ด ๊ฑฐ์ง“, WHERE ... AND ๊ฑฐ์ง“ โ†’ ํ–‰์ด ๋ฐ˜ํ™˜๋˜์ง€ ์•Š๋Š”๋‹ค.
  • ์กฐ๊ฑด์ด ๊ฑฐ์ง“์ด๋ฉด IF๊ฐ€ X(=9329)๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. X=X๋Š” ์ฐธ, WHERE ... AND ์ฐธ โ†’ ์›๋ž˜ ํ–‰์ด ๋ฐ˜ํ™˜๋œ๋‹ค.

์ฆ‰ ์กฐ๊ฑด์˜ ์ฐธ/๊ฑฐ์ง“์ด ๊ฒฐ๊ณผ ํ–‰์˜ ์œ ๋ฌด๋ฅผ ๋ฐ”๊พธ๊ณ , ๊ทธ๊ฑด ๊ณง ์‘๋‹ต ๋ณธ๋ฌธ ํฌ๊ธฐ์˜ ์ฐจ์ด๋กœ ๋กœ๊ทธ์˜ ์‘๋‹ต ๋ฐ”์ดํŠธ ํ•„๋“œ์— ๋‚จ๋Š”๋‹ค. ์ง€์—ฐ์„ ๋ชป ๋ด๋„ ์‘๋‹ต ํฌ๊ธฐ๋กœ ์ฐธ/๊ฑฐ์ง“์„ ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค. ์‹ค์ œ๋กœ ์ถ”์ถœ ์ฟผ๋ฆฌ๋“ค์˜ ์‘๋‹ต ๋ฐ”์ดํŠธ๋ฅผ ์„ธ์–ด ๋ดค๋‹ค.

# oracle.py โ€” ์ถ”์ถœ ์ฟผ๋ฆฌ ์‘๋‹ต ๋ฐ”์ดํŠธ ๋ถ„ํฌ
import collections, urllib.parse
bc = collections.Counter()
for ln in open("access.log"):
    if "SLEEP" in ln and "ORD(MID" in urllib.parse.unquote(ln):
        bc[int(ln.split()[9])] += 1
print(bc.most_common(6))
์‘๋‹ต ๋ฐ”์ดํŠธ ๋ถ„ํฌ โ€” 1664๋ฐ”์ดํŠธ๊ฐ€ 2136ํšŒ๋กœ ์••๋„์ , ๋‚˜๋จธ์ง€๋Š” 1808~1860, 1664๊ฐ€ ์กฐ๊ฑด ์ฐธ(SLEEPยทrow ์—†์Œ) ์‹ ํ˜ธ
์‘๋‹ต ๋ฐ”์ดํŠธ ๋ถ„ํฌ โ€” 1664๋ฐ”์ดํŠธ๊ฐ€ 2136ํšŒ๋กœ ์••๋„์ , ๋‚˜๋จธ์ง€๋Š” 1808~1860, 1664๊ฐ€ ์กฐ๊ฑด ์ฐธ(SLEEPยทrow ์—†์Œ) ์‹ ํ˜ธ

1664 ๋ฐ”์ดํŠธ๊ฐ€ ์••๋„์ ์œผ๋กœ ๋งŽ๊ณ (2136ํšŒ), ๋‚˜๋จธ์ง€๋Š” 1808~1860์— ํฉ์–ด์ ธ ์žˆ๋‹ค. 1664๊ฐ€ ๋ฐ”๋กœ "ํ–‰์ด ์‚ฌ๋ผ์ง„" ์งง์€ ์‘๋‹ต, ์ฆ‰ ์กฐ๊ฑด์ด ์ฐธ์ผ ๋•Œ์˜ ์‹ ํ˜ธ๋‹ค. ์ด๊ฒŒ ์šฐ๋ฆฌ์˜ blind ์˜ค๋ผํด์ด๋‹ค.

โ–ถ ์‚ฝ์งˆ: ์‘๋‹ต์‹œ๊ฐ„์„ ์ฐพ์•„ ํ—ค๋งค๊ณ , flag ํ…Œ์ด๋ธ” ์ด๋ฆ„์— ํ•œ ๋ฒˆ ์†๋‹ค

์ฒ˜์Œ์—” time-based๋ผ๋Š” ๋ง์— ๊ฐ‡ํ˜€ "๋กœ๊ทธ์—์„œ ์‘๋‹ต ์‹œ๊ฐ„์„ ์–ด๋–ป๊ฒŒ๋“  ๋ฝ‘์•„์•ผ ํ•œ๋‹ค"๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค. ๋กœ๊ทธ ํฌ๋งท์„ ๋ช‡ ๋ฒˆ์ด๋‚˜ ๋‹ค์‹œ ๋ณด๋ฉฐ $request_time์ด ์ˆจ์–ด ์žˆ๋‚˜ ๋’ค์กŒ์ง€๋งŒ ์—†์—ˆ๋‹ค. ํ•œ์ฐธ ๋’ค์—์•ผ "์ง€์—ฐ์ด ์•„๋‹ˆ๋ผ ์ฃผ์ž…์ด ๊ฒฐ๊ณผ ์ง‘ํ•ฉ์„ ๋ฐ”๊พธ๋‹ˆ ์‘๋‹ต ํฌ๊ธฐ๋ฅผ ๋ณด๋ฉด ๋œ๋‹ค"๋Š” ๋ฐ ์ƒ๊ฐ์ด ๋ฏธ์ณค๋‹ค. time-based ํŽ˜์ด๋กœ๋“œ์ง€๋งŒ ์‚ฌ์‹ค์ƒ boolean ์˜ค๋ผํด์„ ๊ณต์งœ๋กœ ์–ป๋Š” ์…ˆ์ด์—ˆ๋‹ค.

๋‘ ๋ฒˆ์งธ ์‚ฝ์งˆ์€ ํ…Œ์ด๋ธ” ์ด๋ฆ„์ด์—ˆ๋‹ค. ์ฒ˜์Œ ํŒŒ์‹ฑํ–ˆ์„ ๋•Œ FROM dvwa.flag๋งŒ ๋ˆˆ์— ๋“ค์–ด์™€์„œ "flag ํ…Œ์ด๋ธ”์—์„œ id๋ฐ–์— ์•ˆ ๋นผ๊ฐ”๋„ค?"๋ผ๊ณ  ๊ฒฐ๋ก ์ง€์„ ๋ป”ํ–ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ปฌ๋Ÿผ ์—ด๊ฑฐ ์ฟผ๋ฆฌ์˜ WHERE table_name=0x666c616773์„ ๋””์ฝ”๋“œํ•˜๋‹ˆ 0x666c616773 = "flags"์˜€๋‹ค. flag์™€ flags, ๋‘ ํ…Œ์ด๋ธ”์ด ๋”ฐ๋กœ ์žˆ์—ˆ๋˜ ๊ฒƒ.

>>> bytes.fromhex("666c616773").decode()
'flags'
>>> bytes.fromhex("666c6167").decode()
'flag'

flags ํ…Œ์ด๋ธ”์€ ์ปฌ๋Ÿผ ์ˆ˜๋ฅผ ์„ธ๋ฉด 0์ด ๋‚˜์˜ค๋Š” ๋นˆ ๊ป๋ฐ๊ธฐ(๋””์ฝ”์ด)์˜€๊ณ , ์ง„์งœ ๋ฐ์ดํ„ฐ๋Š” flag ํ…Œ์ด๋ธ”์˜ value ์ปฌ๋Ÿผ์— ์žˆ์—ˆ๋‹ค. ์žฌ๊ตฌ์„ฑ ํ‚ค๋ฅผ FROM ... ์ ˆ ์ „์ฒด(WHERE ํฌํ•จ)๋กœ ์žก์•„ ๋‘ ํ…Œ์ด๋ธ”์„ ๊ตฌ๋ถ„ํ•˜๊ณ  ๋‚˜์„œ์•ผ ๊ฐ’์ด ์ œ๋Œ€๋กœ ๋–จ์–ด์กŒ๋‹ค.



๐Ÿš€ ์ด์ง„ํƒ์ƒ‰ ๋˜๊ฐ๊ธฐ โ€” ๊ณต๊ฒฉ์ž๊ฐ€ ์–ป์€ ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ๊ณ„์‚ฐ

์˜ค๋ผํด์„ ์ •ํ–ˆ์œผ๋‹ˆ ์žฌ๊ตฌ์„ฑ์€ ๊ธฐ๊ณ„์ ์ด๋‹ค. ๊ฐ ์ถ”์ถœ ์ฟผ๋ฆฌ์—์„œ (์ถ”์ถœ ๋Œ€์ƒ, LIMIT ํ–‰, ๋ฌธ์ž ์œ„์น˜)์™€ ORD(MID(...)) ์—ฐ์‚ฐ์ž ๊ฐ’, ๊ทธ๋ฆฌ๊ณ  ์‘๋‹ต ๋ฐ”์ดํŠธ๋ฅผ ๋ฝ‘์•„, ์‘๋‹ต์ด 1664๋ฉด ์กฐ๊ฑด ์ฐธ์œผ๋กœ ๋ณด๊ณ  >ยท<ยท=ยท!=์— ๋งž์ถฐ ๊ทธ ๋ฌธ์ž์˜ ํ•˜ํ•œยท์ƒํ•œ์„ ์ขํžŒ๋‹ค. ์œ„์น˜๋ณ„๋กœ ๊ฐ’์ด ํ•˜๋‚˜๋กœ ํ™•์ •๋˜๋ฉด ๋ฌธ์ž๊ฐ€ ๋‚˜์˜จ๋‹ค.

import urllib.parse, re, collections
lines = open("extracted/access.log").read().splitlines()
 
pat = re.compile(
  r"ORD\(MID\(\(SELECT IFNULL\(CAST\((?P<expr>.+?) AS CHAR\),0x20\) FROM (?P<src>.+?)\),"
  r"(?P<pos>\d+),1\)\)(?P<op>!=|>=|<=|=|>|<)(?P<val>\d+)")
 
bytecnt = collections.Counter(); recs = []
for ln in lines:
    if "SLEEP" not in ln: continue
    q = urllib.parse.unquote(ln)
    m = pat.search(q)
    if not m: continue
    by = int(ln.split()[9]); bytecnt[by] += 1
    src = m.group("src"); rr = re.search(r"LIMIT (\d+),1", src)
    r = int(rr.group(1)) if rr else 0
    recs.append((m.group("expr"), re.sub(r" ORDER BY.*$", "", src), r,
                 int(m.group("pos")), m.group("op"), int(m.group("val")), by))
 
TRUE_BYTES = bytecnt.most_common(1)[0][0]      # ๊ฐ€์žฅ ํ”ํ•œ ์‘๋‹ต = ์กฐ๊ฑด ์ฐธ(SLEEP)
bounds = collections.defaultdict(lambda: [0, 255]); eqs = {}; neq = collections.defaultdict(set)
for expr, src, r, pos, op, val, by in recs:
    cond = (by == TRUE_BYTES); k = (expr, src, r, pos); lo, hi = bounds[k]
    if   op == ">":  lo, hi = (max(lo, val+1), hi) if cond else (lo, min(hi, val))
    elif op == "<":  lo, hi = (lo, min(hi, val-1)) if cond else (max(lo, val), hi)
    elif op == "=":  eqs[k] = val if cond else eqs.get(k)
    elif op == "!=": (neq[k].add(val) if cond else eqs.__setitem__(k, val))
    bounds[k] = [lo, hi]
 
def ch(k):
    if k in eqs and eqs[k] is not None: c = eqs[k]
    else:
        lo, hi = bounds[k]; cand = [x for x in range(max(lo,1), hi+1) if x not in neq[k]]
        c = lo if lo == hi else (cand[0] if len(cand)==1 else None)
    return chr(c) if c and 32 <= c < 127 else "?"
 
groups = collections.defaultdict(dict)
for k in bounds:
    expr, src, r, pos = k; groups[(expr, src, r)][pos] = ch(k)
for k, pm in groups.items():
    if "dvwa.flag" in k[1] and "value" in k[0]:
        flag = "".join(pm[p] for p in sorted(pm))
        print("[+] dvwa.flag.value =", flag[:flag.index("}") + 1])

์ด ์žฌ๊ตฌ์„ฑ๊ธฐ๋ฅผ WHERE ์ ˆ๊นŒ์ง€ ํฌํ•จํ•ด ํ‚ค๋กœ ์žก๊ณ  ๋Œ๋ฆฌ๋ฉด ๊ณต๊ฒฉ์ž๊ฐ€ ํ›‘์€ DB ๊ตฌ์กฐ ์ „์ฒด๊ฐ€ ๋˜์‚ด์•„๋‚œ๋‹ค. ์Šคํ‚ค๋งˆ์™€ flag ํ…Œ์ด๋ธ”์„ ๋ณด๋ฉด ์ •์ฒด๊ฐ€ ๋ถ„๋ช…ํ•˜๋‹ค.

์žฌ๊ตฌ์„ฑ๋œ dvwa ์Šคํ‚ค๋งˆ โ€” ํ…Œ์ด๋ธ” flagยทusersยทguestbook, flag ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ idยทvalue, dvwa.flag.value ์—์„œ ํ”Œ๋ž˜๊ทธ ๋ณต์›
์žฌ๊ตฌ์„ฑ๋œ dvwa ์Šคํ‚ค๋งˆ โ€” ํ…Œ์ด๋ธ” flagยทusersยทguestbook, flag ํ…Œ์ด๋ธ” ์ปฌ๋Ÿผ idยทvalue, dvwa.flag.value ์—์„œ ํ”Œ๋ž˜๊ทธ ๋ณต์›

dvwa ์Šคํ‚ค๋งˆ์—” flag, users, guestbook ์„ธ ํ…Œ์ด๋ธ”์ด ์žˆ๊ณ , flag ํ…Œ์ด๋ธ”์€ idยทvalue ๋‘ ์ปฌ๋Ÿผ์„ ๊ฐ€์ง„๋‹ค. ๊ทธ value๊ฐ€ DH{anA1yz1nGVe3yB19L0g} โ€” ํ”Œ๋ž˜๊ทธ๋‹ค. leet๋ฅผ ํ’€๋ฉด "analyzing very big log", ๋”ฑ ์ด ๋ฌธ์ œ ๊ทธ ์ž์ฒด๋‹ค.

์žฌ๊ตฌ์„ฑ์ด ์ œ๋Œ€๋กœ ๋๋Š”์ง€ ํ™•์ธ ์‚ผ์•„ users ํ…Œ์ด๋ธ”๋„ ๋–  ๋ดค๋‹ค. ๊ณต๊ฒฉ์ž๋Š” flag๋งŒ์ด ์•„๋‹ˆ๋ผ ์‚ฌ์šฉ์ž ํ…Œ์ด๋ธ”์„ ํ†ต์งธ๋กœ ๋นผ ๊ฐ”๋‹ค.

์žฌ๊ตฌ์„ฑํ•œ dvwa.users โ€” 1337/admin/gordonb/pablo/smithy ๊ณ„์ •๊ณผ MD5 ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹œ, 1337ยทHack Me๋Š” ์ถœ์ œ์ž๊ฐ€ ์‹ฌ์€ ๊ณ„์ •
์žฌ๊ตฌ์„ฑํ•œ dvwa.users โ€” 1337/admin/gordonb/pablo/smithy ๊ณ„์ •๊ณผ MD5 ๋น„๋ฐ€๋ฒˆํ˜ธ ํ•ด์‹œ, 1337ยทHack Me๋Š” ์ถœ์ œ์ž๊ฐ€ ์‹ฌ์€ ๊ณ„์ •

DVWA ๊ธฐ๋ณธ ๊ณ„์ •๋“ค(admin, gordonb, โ€ฆ)์˜ MD5 ํ•ด์‹œ๊นŒ์ง€ ์ •ํ™•ํžˆ ๋ณต์›๋œ๋‹ค. ์žฌ๊ตฌ์„ฑ ๋กœ์ง์ด ์˜ฌ๋ฐ”๋ฅด๋‹ค๋Š” ๋ฐฉ์ฆ์ด๋‹ค.


Full Exploit

์œ„ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๊ทธ๋Œ€๋กœ ์‹คํ–‰ํ•˜๋ฉด ํ”Œ๋ž˜๊ทธ๊ฐ€ ๋‚˜์˜จ๋‹ค.

solve.py ์‹คํ–‰ โ€” dvwa.flag.value ์žฌ๊ตฌ์„ฑ ๊ฒฐ๊ณผ๋กœ ํ”Œ๋ž˜๊ทธ ์ถœ๋ ฅ
solve.py ์‹คํ–‰ โ€” dvwa.flag.value ์žฌ๊ตฌ์„ฑ ๊ฒฐ๊ณผ๋กœ ํ”Œ๋ž˜๊ทธ ์ถœ๋ ฅ
[+] dvwa.flag.value = DH{anA1yz1nGVe3yB19L0g}


๐Ÿ“ ๊ฒฐ๋ก 

time-based blind๋ผ๋„ ์‘๋‹ต ํฌ๊ธฐ๊ฐ€ ์˜ค๋ผํด์ด ๋  ์ˆ˜ ์žˆ๋‹ค.

์ด ๋ฌธ์ œ์˜ ํ•ต์‹ฌ์€ "์ง€์—ฐ์„ ๋ชป ๋ณด๋Š”๋ฐ ์–ด๋–ป๊ฒŒ ์ฐธ/๊ฑฐ์ง“์„ ์ฝ์ง€?"์˜€๋‹ค. ๋‹ต์€ ํŽ˜์ด๋กœ๋“œ์˜ ๋ถ€์ž‘์šฉ์— ์žˆ์—ˆ๋‹ค. AND X=IF(์กฐ๊ฑด, SLEEP, X)๋Š” ์กฐ๊ฑด์— ๋”ฐ๋ผ WHERE์˜ ๊ฒฐ๊ณผ ํ–‰ ์œ ๋ฌด๋ฅผ ๋ฐ”๊พธ๊ณ , ๊ทธ๊ฒŒ ์‘๋‹ต ํฌ๊ธฐ ์ฐจ์ด๋กœ ๋กœ๊ทธ์— ๋‚จ๋Š”๋‹ค. blind SQLi๋ฅผ ๋กœ๊ทธ๋กœ ์—ญ์ถ”์ ํ•  ๋• ์‹œ๊ฐ„๋ฟ ์•„๋‹ˆ๋ผ ์‘๋‹ต ๋ฐ”์ดํŠธยท์ƒํƒœ ์ฝ”๋“œยท์‘๋‹ต ๊ธธ์ด์˜ ๋ฏธ์„ธํ•œ ์ฐจ์ด๊ฐ€ ์ „๋ถ€ ์˜ค๋ผํด ํ›„๋ณด๋‹ค.

๊ณต๊ฒฉ ์ž๋™ํ™” ๋„๊ตฌ๋Š” ๋กœ๊ทธ์— ์ž๊ธฐ ํ–‰์ ์„ ์ด˜์ด˜ํžˆ ๋‚จ๊ธด๋‹ค.

sqlmap์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋นผ๋‚ด๋ ค๊ณ  ์ˆ˜์ฒœ ๋ฒˆ์˜ ์š”์ฒญ์„ ์ˆœ์ฐจ์ ์œผ๋กœ ๋˜์กŒ๊ณ , ๊ทธ ์ด์ง„ํƒ์ƒ‰ ๊ณผ์ •์ด ๊ณ ์Šค๋ž€ํžˆ ๋กœ๊ทธ์— ์ ํ˜”๋‹ค. ๋ฐฉ์–ด์ž ์ž…์žฅ์—์„  ์ด๊ฒŒ ์–‘๋‚ ์˜ ๊ฒ€์ด๋‹ค. blind SQLi๋Š” ํ•œ ๋ฒˆ์˜ ๊ณต๊ฒฉ์œผ๋กœ ๋๋‚˜์ง€ ์•Š๊ณ  ๋Œ€๋Ÿ‰์˜ ๊ทœ์น™์ ์ธ ์š”์ฒญ์„ ๋‚จ๊ธฐ๋ฏ€๋กœ, ๊ฐ™์€ ์—”๋“œํฌ์ธํŠธ์— ์งง์€ ์‹œ๊ฐ„ ์ˆ˜์ฒœ ๊ฑด, ๋™์ผ UA, SLEEPยทORDยทinformation_schema ๊ฐ™์€ ํ‚ค์›Œ๋“œ๊ฐ€ ๋ณด์ด๋ฉด ์ฆ‰์‹œ ํƒ์ง€ยท์ฐจ๋‹จํ•ด์•ผ ํ•œ๋‹ค. ๋ฐ˜๋Œ€๋กœ ์‚ฌ๊ณ  ๋ถ„์„ ๋‹จ๊ณ„์—์„  ๊ทธ ๊ทœ์น™์„ฑ ๋•์— ๊ณต๊ฒฉ์ž๊ฐ€ ๋ฌด์—‡์„ ์–ผ๋งˆ๋‚˜ ๋นผ ๊ฐ”๋Š”์ง€๊นŒ์ง€ ์ •ํ™•ํžˆ ๋ณต์›ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋กœ๊ทธ๋ฅผ combined ํฌ๋งท ๊ทธ๋Œ€๋กœ ๋‘์ง€ ๋ง๊ณ  $request_timeยท์š”์ฒญ ๋ฐ”๋””๊นŒ์ง€ ๋‚จ๊ธฐ๋ฉด, ์ด๋Ÿฐ ์—ญ์ถ”์ ์€ ๋” ์‰ฌ์›Œ์ง„๋‹ค.

์ด ๊ธ€์ด ๋„์›€์ด ๋๋‚˜์š”?

Comments

๋Œ“๊ธ€

0๊ฐœ

๋Œ“๊ธ€์„ ๋‚จ๊ธฐ๋ ค๋ฉด ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ด์š”. (๋„ค์ด๋ฒ„ ยท ๊ตฌ๊ธ€ ๊ณ„์ •)

๋Œ“๊ธ€ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘โ€ฆ

Related

๊ด€๋ จ ๊ธ€

3๊ฐœ
[๐Ÿฅˆ Silver 3] ASCII()๊ฐ€ ๊ฑฐ์ง“๋ง์„ ํ•˜๋Š” ๋ธ”๋ผ์ธ๋“œ SQLi โ€” DreamHack blind sql injection advanced ํ’€์ด
blog

[๐Ÿฅˆ Silver 3] ASCII()๊ฐ€ ๊ฑฐ์ง“๋ง์„ ํ•˜๋Š” ๋ธ”๋ผ์ธ๋“œ SQLi โ€” DreamHack blind sql injection advanced ํ’€์ด

uid ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ SQL์— ๊ทธ๋Œ€๋กœ ์‚ฝ์ž…๋˜๋Š” boolean blind SQLi๋ฅผ ์ž๋™ํ™”ํ•˜๋‹ค๊ฐ€, ํ•œ๊ธ€์ด ์„ž์ธ ํ”Œ๋ž˜๊ทธ ๋•Œ๋ฌธ์— ASCII() ์ด์ง„ํƒ์ƒ‰์ด ๋์—†์ด ์ฐธ์œผ๋กœ ๋‚˜์˜ค๋Š” ํ˜„์ƒ์— ๋ถ€๋”ชํ˜”๋‹ค. MySQL์˜ ASCII()์™€ ORD()๊ฐ€ ๋ฉ€ํ‹ฐ๋ฐ”์ดํŠธ ๋ฌธ์ž๋ฅผ ๋‹ค๋ฃจ๋Š” ๋ฐฉ์‹ ์ฐจ์ด๋ฅผ ํŒŒ๊ณ ๋“ค์–ด ์šฐํšŒํ•œ ๊ณผ์ •์„ ์ •๋ฆฌํ•œ๋‹ค.
#dreamhack#ctf#web+4
2026-07-04#dreamhack +3
[๐Ÿฅ‰ ๋ธŒ๋ก ์ฆˆ 1] CAN ๋ฒ„์Šค ๋กœ๊ทธ์—์„œ ํ›”์นœ ์ฐจ์˜ ์ •์ฒด โ€” DreamHack Grand Theft Auto ํ’€์ด
blog

[๐Ÿฅ‰ ๋ธŒ๋ก ์ฆˆ 1] CAN ๋ฒ„์Šค ๋กœ๊ทธ์—์„œ ํ›”์นœ ์ฐจ์˜ ์ •์ฒด โ€” DreamHack Grand Theft Auto ํ’€์ด

์ž๋™์ฐจ CAN ๋ฒ„์Šค ์บก์ณ candump.txt ํ•œ ๊ฐœ๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. ๋กœ๊ทธ๋Š” UDS ์ง„๋‹จ ์„ธ์…˜์œผ๋กœ, ์ง„๋‹จ๊ธฐ๊ฐ€ ReadDataByIdentifier(0x22)๋กœ DID 0xF190(VIN)์„ ์š”์ฒญํ•˜๊ณ  ECU๊ฐ€ ISO-TP ๋ฉ€ํ‹ฐํ”„๋ ˆ์ž„์œผ๋กœ ์‘๋‹ตํ•œ๋‹ค. ํ”„๋ ˆ์ž„์„ ์žฌ์กฐ๋ฆฝํ•ด VIN์„ ์–ป๊ณ , ๊ทธ VIN์„ ์—ฐ๋„ยท์ œ์กฐ์‚ฌยท๋ชจ๋ธ๋กœ ๋””์ฝ”๋“œํ•ด ํ”Œ๋ž˜๊ทธ๋ฅผ ์™„์„ฑํ–ˆ๋‹ค.
#dreamhack#ctf#forensics+4
2026-07-13#dreamhack +3
[๐Ÿฅ‰ Bronze 4] ๋ฐ˜ํˆฌ๋ช… ๋ง์น ์€ ์ง€์šฐ๊ฐœ๊ฐ€ ์•„๋‹ˆ๋‹ค โ€” DreamHack QR Code ํ’€์ด
blog

[๐Ÿฅ‰ Bronze 4] ๋ฐ˜ํˆฌ๋ช… ๋ง์น ์€ ์ง€์šฐ๊ฐœ๊ฐ€ ์•„๋‹ˆ๋‹ค โ€” DreamHack QR Code ํ’€์ด

QR ์ฝ”๋“œ ์œ„์— ๊ฒ€์€ ๋‚™์„œ๊ฐ€ ๋ฎ์—ฌ ์žˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ํ”ฝ์…€๊ฐ’์ด ์—ด ์ข…๋ฅ˜๋ฐ–์— ์—†๊ณ  255, 63, 16, 4, 1 ์ด๋ผ๋Š” ๋“ฑ๋น„ ์‚ฌ์Šฌ์„ ์ด๋ฃฌ๋‹ค. ์•ŒํŒŒ 75% ๊ฒ€์ •์„ ๊ฒน์น  ๋•Œ๋งˆ๋‹ค ๋ฐ‘์˜ ๋ฐ๊ธฐ๊ฐ€ 1/4 ์”ฉ๋งŒ ๋‚จ์€ ํ”์ ์ด๋‹ค. "ํ”ฝ์…€๊ฐ’์ด 0๋ณด๋‹ค ํฌ๋ฉด ํฐ ๋ชจ๋“ˆ" ํ•œ ์ค„๋กœ 29x29 ๊ฒฉ์ž๋ฅผ ๋˜์‚ด๋ฆฌ๊ณ , ์žฌ์ธ์ฝ”๋”ฉ๋ณธ๊ณผ 841์นธ ์ „์ˆ˜ ๋Œ€์กฐ๊นŒ์ง€ ํ–ˆ๋‹ค.
#dreamhack#ctf#misc+5
2026-08-22#dreamhack +3