Zed Attack Proxy (ZAP) ์ฌ์ฉ ๊ฐ์ด๋ : [Dreamhack] web-ssrf ํ์ด
๐ ZAP์ ์ฌ์ฉ๋ฒ์ ๋ค์ ๋์ค๋ ๋ด๋ ค๊ฐ์ฃผ์ธ์ ๐
web-ssrf ๋ผ๋ ๋ฌธ์ ์ ๋๋ค.
๐ก SSRF ๋ ?
Server-side Request Forgery(SSRF)๋ ์น ์๋น์ค์ ์์ฒญ์ ๋ณ์กฐํ๋ ์ทจ์ฝ์ ์ผ๋ก,
์น์๋ฒ์ ์์กฐ๋ ์์ฒญ์ ๋ณด๋ด๋ฉด, ํด๋น ์น์๋ฒ๊ฐ ๋ด๋ถ ์๋ฒ์ ํต์ ์ ์ํํ๋๋ก ์กฐ์ํ ์ ์์ต๋๋ค.
์ด๋ฅผ ํตํด ์ฌ์ฉ์๋ ์ง์ ์ ๊ทผํ ์ ์๋ ๋ด๋ถ ์์์ ๋ํด ๊ฐ์ ์ ์ผ๋ก ๊ณต๊ฒฉ์ ์ํํ ์ ์์ต๋๋ค.
๐ ์์ค์ฝ๋ ๋ถ์
๐๏ธ ์ ์ฒด ์ฝ๋
๐ /img_viewer
@app.route("/img_viewer", methods=["GET", "POST"])
def img_viewer():
if request.method == "GET":
return render_template("img_viewer.html")
elif request.method == "POST":
url = request.form.get("url", "")
urlp = urlparse(url)
if url[0] == "/":
url = "http://localhost:8000" + url
elif ("localhost" in urlp.netloc) or ("127.0.0.1" in urlp.netloc):
data = open("error.png", "rb").read()
img = base64.b64encode(data).decode("utf8")
return render_template("img_viewer.html", img=img)
try:
data = requests.get(url, timeout=3).content
img = base64.b64encode(data).decode("utf8")
except:
data = open("error.png", "rb").read()
img = base64.b64encode(data).decode("utf8")
return render_template("img_viewer.html", img=img)
/img_viewer ์์๋ POST ์์ฒญ ์ url ์ด๋ผ๋ ํ๋ผ๋ฏธํฐ ๊ฐ์ ์ถ์ถ, urlparse ํด์ urlp์ ์ ์ฅํฉ๋๋ค. ์ด๋ urlp์ ํํ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
โ urlp์ ํํ
ParseResult(
scheme='https', # URL ์คํค๋ง (http, https ๋ฑ)
netloc='example.com', # ๋คํธ์ํฌ ์์น (๋๋ฉ์ธ ๋ฐ ํฌํธ)
path='/path', # URL ๊ฒฝ๋ก
params='', # ๊ฒฝ๋ก ๋ด ๋งค๊ฐ๋ณ์ (์ ์ฌ์ฉ๋์ง ์์)
query='query=123', # ์ฟผ๋ฆฌ ๋ฌธ์์ด
fragment='fragment' # ํ๋๊ทธ๋จผํธ (# ๋ค์ ๊ฐ)
)
url์ด "/" ์ผ๋ก ์์ํ๋ฉด, "http://localhost:8000" + url ํํ๋ก url์ ๋ถ์ฌ์ ์์ฒญ์ ๋ณด๋ด๊ณ data ๋ณ์์ ์ ์ฅ,
data๋ base64๋ก ์ธ์ฝ๋ฉ ํ utf-8๋ก ๋์ฝ๋ฉ ํด img ๋ณ์์ ์ ์ฅํฉ๋๋ค. ๋ง์ง๋ง์ผ๋ก img๋ฅผ returnํ๊ฒ ๋ฉ๋๋ค.
ํ์ง๋ง "/" ์ผ๋ก ์์ํ์ง ์๋๋ค๋ฉด urlp.netloc์ด localhost ํน์ 127.0.0.1 ์ผ ์ error.png๋ฅผ data์ ์ ์ฅํ๊ณ ,
data๋ฅผ base64๋ก ์ธ์ฝ๋ฉ, utf-8๋ก ๋์ฝ๋ฉํด returnํ๊ฒ ๋ฉ๋๋ค.
๐ local_host
local_host = "127.0.0.1"
local_port = random.randint(1500, 1800)
local_server = http.server.HTTPServer(
(local_host, local_port), http.server.SimpleHTTPRequestHandler
)
print(local_port)
์ฝ๋์๋ local_host ์ local_port๋ฅผ ์ค์ ํ๋ ๋ถ๋ถ์ด ํฌํจ๋ผ ์์ต๋๋ค.
local_host ๋ 127.0.0.1 ๋ก ์๋ฒ ์๊ธฐ ์์ ์ผ๋ก ์ง์ ํ๊ณ , local_port ๋ 1500 ~ 1800 ์ฌ์ด์ ๊ฐ์ผ๋ก ๋๋คํ๊ฒ ์ง์ ํ๋ ๋ชจ์ต์
๋๋ค.
์๋ง FLAG ๋ ๋ด๋ถ ์๋ฒ์ ์์นํ ๊ฒ์ผ๋ก ์ถ์ ๋ฉ๋๋ค. (SSRF ๋ฌธ์ ๋๊น)
๐ ๋ด๋ถ PORT ์ฐพ๊ธฐ
์ฐ์ localhost ์ 127.0.0.1 ์ด๋ผ๋ ํค์๋๋ฅผ ํํฐ๋งํ๊ณ ์์ผ๋ฏ๋ก ์ด ๋ ํค์๋๋ฅผ ์ฌ์ฉํ์ง ์์ผ๋ฉด์ ๊ฐ์ ์๋ฏธ๋ฅผ ์ง๋ ํค์๋๋ฅผ ์ฐพ์์ผ ํฉ๋๋ค.
๋์๋ฌธ์๋ฅผ ๊ตฌ๋ถํ์ง ์์ผ๋ฏ๋ก Localhost๋ก ์ฐํํ ์ ์๊ณ , 16์ง์๋ก ๋ณํํด 7F.00.00.01 ์ผ๋ก ํํํ ์๋ ์์ต๋๋ค.
๋ 0.0.0.0 ๋ ์ฌ์ฉ ๊ฐ๋ฅํฉ๋๋ค.
์ ๋ ์ด๋ฒ์ 0.0.0.0์ ์ฌ์ฉํด ๋ณด๊ฒ ์ต๋๋ค.
์ด๋ ค์๋ ํฌํธ์ผ ๊ฒฝ์ฐ IMG๊ฐ ํ์๋๊ณ , ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ NOT FOUND๋ผ๋ IMG๋ฅผ ๋ฐํํ๋ฏ๋ก Response Length๋ฅผ ๋น๊ตํด์ ์ด๋ ค์๋ ํฌํธ๋ฅผ ์ฐพ์ ๋ณด๊ฒ ์ต๋๋ค.
โก๏ธ Zed Attack Proxy (ZAP) ์ฌ์ฉ
ํ์ด์ฌ์ผ๋ก requests ๋ชจ๋์ ์ฌ์ฉํด ์ด๋ ค์๋ ํฌํธ๋ฅผ ์ฐพ์ ์๋ ์๊ณ , Burp Suite์ Intruder ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์๋ ์์ง๋ง, ๋๋ฆฌ๊ธฐ ๋๋ฌธ์ ZAP์ ์ด์ฉํด ์ด๋ ค ์๋ ํฌํธ๋ฅผ ์ฐพ์๋ณด๊ฒ ์ต๋๋ค.
๐ OWASP ZAP ์ด๋ ?
-> Zed Attack Proxy์ ์ฝ์๋ก, ์คํ ์์ค ์น ์ ํ๋ฆฌ์ผ์ด์ ๋ณด์ ์ค์บ๋์ ๋๋ค. ํฌํธ ์ค์บ, ์ทจ์ฝ์ ๋ถ์, ๋ณด๊ณ ์ ์์ฑ, ์น ํ๋ก์ ๋ฑ ๋ง์ ๊ธฐ๋ฅ์ด ์์ต๋๋ค.
The ZAP Homepage
Welcome to ZAP!
www.zaproxy.org
์ ๋ ํ์ด์ดํญ์ค์ ํ๋ก์ ์ค์ ์ ํด์ฃผ์์ต๋๋ค.
Break ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ ค๋ฉด ๋จผ์ ํ๋ก์ ์ค์ ์ด ๋์ด์์ด์ผ ํ๋๋ฐ,
ํ์ด์ดํญ์ค ๋ด์์ ์ค์ -> ๋คํธ์ํฌ ์ค์ -> ์๋ ํ๋ก์ ์ค์ - 127.0.0.1, ํฌํธ 8081 ์ค์ ํด์ฃผ๊ณ ,
ZAP Options -> Network - Server Cerificates์์ ์ธ์ฆ์ ์ ์ฅ ํ
ํ์ด์ดํญ์ค ์ค์ -> ๊ฐ์ธ ์ ๋ณด ๋ฐ ๋ณด์ -> ์ธ์ฆ์ -> ์ธ์ฆ์ ๋ณด๊ธฐ -> ๊ฐ์ ธ์ค๊ธฐ ์์ zap_root_ca.cer์ ๋ฑ๋กํด ์ค๋๋ค.
์ค์ ์ ๋๋ง์น๊ณ
ZAP์ ํ๋ฉด ์ ๋๋ค. ์๋จ์ ๋ณด์๋ฉด
์ด๋ฐ ์๋จ๋ฐ ๊ฐ ์๋๋ฐ, ์ฒซ ๋ น์ ๋ฒํผ์ด Break ๋ฒํผ์ ๋๋ค. ํด๋ฆญํ๊ณ ํ์ด์ดํญ์ค๋ฅผ ์ฌ์ฉํด๋ณด๋ฉด
์ด๋ ๊ฒ ์์ฒญ์ด ์กํ๊ฒ ๋ฉ๋๋ค. ์ ๊ธฐ์ ์์ฒญ์ ์์ ํ ์ ์์ต๋๋ค.
๋ฌธ์ input์ http://0.0.0.0:1500์ ๋ฃ์ด ์์ฒญํด ๋ณด์์ต๋๋ค. Response Length๊ฐ 65121์ธ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
์ฌ๊ธฐ์ ์ฐํด๋ฆญ - Fuzz ํ๋ฉด
Fuzzer ์ฐฝ์ด ๋ฐ ํ ๋ฐ
์ฌ๊ธฐ์ ์ฆ๊ฐํ๋ ์นด์ดํฐ ๋ถ๋ถ์ ๋๋๊ทธ ํ๊ณ (์ฌ๊ธฐ์๋ 1500) -> Payloads -> Add ํด๋ฆญ
Type์ Numberzz๋ก ์ ํํ๊ณ ์์์ ๊ณผ ์ถ๋ฐ์ ์ ์ง์ ํด์ฃผ๊ณ Start Fuzzer ๋ฅผ ํด๋ฆญํด์ฃผ๋ฉด
๊ต์ฅํ ๋น ๋ฅด๊ฒ request๋ฅผ ๋ณด๋ด๋ ๋ชจ์ต์ ๋ณผ ์ ์์ต๋๋ค. response length๊ฐ ํน๋ณํ ์งง์ 1649๊ฐ ๋๋คํ๊ฒ ์ด๋ฆฐ ํฌํธ๊ฒ ๋ค์.
response ์ ๋๋ค. base64๋ก ์ธ์ฝ๋ฉ๋์ด ์๋๋ฐ ์ด๋ฅผ ๋ณํํ๋ฉด ์๋์ ๊ฐ์ต๋๋ค.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href="app.py">app.py</a></li>
<li><a href="error.png">error.png</a></li>
<li><a href="flag.txt">flag.txt</a></li>
<li><a href="requirements.txt">requirements.txt</a></li>
<li><a href="static/">static/</a></li>
<li><a href="templates/">templates/</a></li>
</ul>
<hr>
</body>
</html>
flag.txt ๊ฐ ์์ผ๋ฏ๋ก http://0.0.0.0:1649/flag.txt ์ ์ ๊ทผํด๋ณด๋ฉด?
Base64๋ก ์ธ์ฝ๋ฉ๋ flag๊ฐ ๋์ค๊ฒ ๋ฉ๋๋ค.