flask로 작성된 image viewer 서비스 입니다.
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)
local_host = "127.0.0.1"
local_port = random.randint(1500, 1800)
local_server = http.server.HTTPServer(
(local_host, local_port), http.server.SimpleHTTPRequestHandler
)
소스코드를 분석해보면
.netloc은 도메인 이름 + 포트 번호를 포함하는 urlparse() 함수의 일부분으로, 로컬주소가 url 부분에 있으면 내부 자원 접근 시도로 간주하고 ssrf를 방지하는 코드가 있다. → 우회 필요request.get() 으로 외부에서 접근 시 이미지 데이터를 base64로 인코딩해서 표시한다.문제 풀이
우선 로컬호스트(127.0.0.1)로 접근하기 위해서는 에러메시지를 피해 우회해주어야 하기 때문에 인코딩 우회 기법을 사용하여 127.0.0.1을 16진수로 표현한 0x7f000001 를 사용하여 접근한다. (내부적으로 사용하는 파이썬 네트워크 라이브러리(socket)가 IP를 해석함)
또한 로컬 포트를 모르기 때문에 1500~1800사이의 포트를 찾기 위해 버프스위트를 통해 브루트포스 공격으로 8000번 포트와 동일한 결과를 출력하는 번호를 찾는다.

길이가 다른 포트 하나를 확인할 수 있고, 이 포트를 이용해 flag.txt 로 접근하면 flag 획득 성공

