DreamHack/WEB

web-ssrf

G_OM 2024. 9. 26. 17:13

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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


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)

 

 

문제 전체 코드이다.

 

문제 사이트도 들어가 보자.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

image Viewer에 들어가서 url 안에 있는 경로를 view 버튼을 눌렀더니 png 가 나온다.

 

뭐 할거 없으니 중요 코드를 보러 가자

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

url = request.form.get("url", "")
urlp = urlparse(url)

 

url에 대해서 POST 요청으로 처리가 된다.

 

 

 

 

 

 

 

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)

 

 

if 문에서는 URL에 대해서 상대경로인지 확인한다.

 

만약 상대경로이면 절대경로인 "http://localhost:8000"으로 바꾼다.

 

elif에서는 urlp.netloc을 사용하여 "localhost" , "127.0.0.1"을 확인한다.

 

조건에 맞다면 error.png를 불러온다.

 

error.png 이미지를 가져올 때 base64 인코딩을 해서 표시한다.

 

 

 

 

 

 

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)

 

 

다르게 URL 이 로컬 호스트(127.0.0.1 , localhost)가 아닐 경우, error.png를 출력한다.

 

똑같이 base64 인코딩해서 표시한다.

 

 

 

 

 

 

 

 

local_host = "127.0.0.1"
local_port = random.randint(1500, 1800)
local_server = http.server.HTTPServer(
    (local_host, local_port), http.server.SimpleHTTPRequestHandler
)
print(local_port)

 

local_host는 127.0.0.1로 설정

 

port 은 1500~1800 사이의 랜덤포트 번호이다.

 

local_server는 HTTP 서버를 설정한다.

 

서버에 들어가 /flag.txt를 출력하려면

http:로컬호스트:{랜덤포트번호}를 만족시켜야 한다.

 

로컬호스트는 "localhost" , "127.0.0.1" 으로만 막아둔 상태이다.

 

그러면 로컬호스트는 "Localhost" , "LOCALHOST" , "0.0.0.0" 으로 우회가 가능하다.

 

나머지 포트번호를 찾기 위해서는 1500~1800 에다가 브루트 포스 연산을 넣어야하는데

 

조건을 찾아야한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

다시 돌아와

 

정상적으로 url 에 dream.png 를 불러오면 image Viewer 가 잘 불러와준다.

 

우리는 아까 소스코드에서 image 를 불러올때 base64 로 표시가 된다는걸 확인했다.

 

프록시로 보면...

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

확인해보면 정상적으로 url 를 보냈을때 image 가 base64 인코딩된걸 확인할수있다.

 

이것은 error.png 도 마찬가지로 base64 로 인코딩되서 나온다.

 

decode 를 해보면....

 

 

 

 

 

 

 

 

 

 

 

error.png , dream.png 전부 decode 할때 동일하게 PNG 가 decode 된걸 확인할 수 있다.

 

그렇다면 저 PNG 부분만 걸러낸다면 포트번호를 확인할 수 있을거 같다.

 

왜냐하면

 

포트번호가 실패한다면 error.png 가 출력되면서 PNG 가 나오기 때문이다.

 

그러면 종합적으로 보면 localhost 우회하고 포트번호를 찾을수 있는 조건을 알아낸것이다.

 

 

 

 

 

 

 

 

 

PNG 만 걸러내도록 하자.

 

 

 

 

 

 

나 같은 경우는 1582 포트 가 열려있었다.

 

 

 

 

 

 

 

 

 

 

 

 

localhost 우회, 찾은 포트 를 입력하니 flag.txt 를 가져왔다.

 

flag.txt 를 여전히 image 에서 가져오기 때문에 base64 인코딩이 되어 있는 상태이다.