본문 바로가기

ETC/EVI$ION

EVI$ION 러닝 세션 과제 - #4 (5/11)

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

 

web-ssrf

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

dreamhack.io

 

SSRF 취약점 문제이다

내가 스스로 풀 수 있을리가 없으니 풀이를 보고 천천히 따라해 보았다 *^^*

 

일단 SSRF는 클라이언트 측 요청을 변조시키는 것이 아니라 서버측 자체의 요청을 변조하여 공격자가 원하는 형태의 악성 행위를 서버에 던져주면 서버가 검증 없이 그대로 받아 그에 따른 행위/응답을 해주는 공격을 말한다.

주로 사용자가 입력을 받아 서버가 직접 다른 웹이나 포트에 접근해서 데이터를 가져오는 기능에서 발생하며,

외부가 아닌 내부의 공격을 수행하게 되므로 접근제어 정책을 우회할 수 있는 공격이다.

 

일단 View를 눌러보았다.

 

오호.. 이런 드림핵 로고 이미지가 나타난다.

 

일단 소스코드를 봐보자.

#!/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()  
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)

 

url에 입력된 값의 첫번째가 /이면 url=" "http://localhost:8000" + url가 되고

입력된 값에 "localhost" 또는 "127.0.01"이 포함되어 있으면 error.png를 반환하고 함수를 종료한다.

 

궁금하니까 해봐야겠다.

 생각보다 더 큰 이미지가 나와서 놀랐다...

 

다시 소스코드를 보자.

함수가 종료되지 않으면, data값에 localhost에 위치한 url에 입력한 값에 해당하는 경로의 값을 가져오고

해당 값을 base64로 디코딩해 이미지를 출력한다.

 

local port는 1500-1800의 랜덤 값이다.

 

따라서 flag.txt는 http://127.0.0.1:{랜덤포트값}/flag.txt로 접근해야한다고 한다..

하지만 또 127.0.0.1이 필터링되기 때문에 우회를 해야한다.

우회를 하는 방법이 여러가지가 있는데 이 중 가장 단순한 Localhost를 이용해보자.

 

이제 저 local port 번호를 알아내기 위해서 Burp Suite를 이용해보자

 

저 부분에 다가 1500부터 1800을 대입해서 local port 값을 찾아내보자

 

300개를 다 해야 나오려나 생각하던 참에

다른 값들과 다른게 튀어나왔다!!

local port는 1664라는 것을 알 수 있다.

 

 

이제 경로를 /flag.txt로 설정해서 Request를 보내면

저런 base64로 인코딩된 flag값이 나온다

 

 

디코딩을 하니 flag가 나왔다!

'ETC > EVI$ION' 카테고리의 다른 글

EVI$ION 정규 세션 과제 - #6  (0) 2024.05.23
EVI$ION 정규 세션 과제 - #5  (0) 2024.05.16
EVI$ION 정규 세션 과제 - #4  (0) 2024.05.09
EVI$ION 러닝 세션 과제 - #3 (5/4)  (0) 2024.05.04
EVI$ION 러닝 세션 과제 - #2 (4/6)  (1) 2024.04.06