https://dreamhack.io/wargame/challenges/75
flask로 작성된 image viewer 서비스 입니다.
SSRF 취약점을 이용해 플래그를 획득하세요. 플래그는 /app/flag.txt에 있습니다.
SSRF란?
- 서버가 의도하지 않은 곳으로 요청을 보내도록 속이는 공격이다.
- 웹 애플리케이션이 사용자가 입력한 URL 또는 리소스 위치를 검증 없이 그대로 요청할 때 발생한다. 공격자는 이를 악용하여 내부 또는 외부의 의도치 않은 대상에 요청을 보낼 수 있다.
- 방화벽, VPN 등으로 보호되던 시스템도 SSRF 공격을 통해 우회될 수 있으며, 취약점의 심각성은 클라우드 서비스와 복잡한 인프라가 확산됨에 따라 커지고 있다.
- 최신 웹 애플리케이션은 사용자의 편리한 기능을 제공하고자 URL 입력이 일반적인 상황이 되었다. 이로 인해 SSRF 발생률이 증가하고 있다.
사이트에 접속해보았다.

url을 입력받고, 그에 대한 이미지를 출력해주는 사이트임을 알 수 있다.
혹시 몰라 url에 flag가 있다는 /app/flag.txt 를 입력해보았지만...

아무것도 뜨지 않는다. 혹시 몰라 경로까지 바꿔보았지만,

역시나 풀리지 않았다.
코드를 확인해보았다.
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)
local_host = "127.0.0.1"
local_host가 내부에서만 접근 가능한 127.0.0.1로 설정되어 있고, url에 localhost나 127.0.0.1가 포함되면 무조건 에러 이미지를 띄운다는걸 알 수 있었다. 즉, 외부에서 접근하는것은 불가능함을 알 수 있었고, 이를 우회해야한다.
127.0.0.1를 우회하기 위해 값을 10진수인 2130706433로 바꾸어보았다.

위처럼 주소를 숫자로 바꾸어 입력해도 이미지가 출력되는 것을 확인할 수 있었다!
이는 브라우저에서는 주소나 주소를 10진수로 변환한 것이나 똑같은 주소라고 생각을 해 정상적으로 접속이 가능하고, 필터에서는 127.0.0.1라는 숫자가 아니기에 통과가 되는 것이다.
코드를 확인해보면,
local_port = random.randint(1500, 1800)
로컬 포트는 1500과 1800 사이 숫자에 있다고 한다. http://2130706433:8000/static/dream.png
에서 8000을 1500과 1800사이의 로컬 호스트 포트번호로 변경해주면 된다.
로컬호스트 포트번호를 찾기위해 burp suite를 사용해 무차별 대입 공격을 해준다.
프록시로 웹사이트를 intercept하고, url에 url=http://2130706433:8000/flag.txt를 입력 후 veiw를 누른 상태로 request 값을 우클릭해 Send to Intruder 해준다.

이후 Intruder에 옮겨와 포트번호 8000을 드래그 하고, 우클릭해 add payload position 을 클릭한다.
오른쪽에 있는 payload를 클릭해 payload type은 number, 범위는 1500과 1800까지로 설정한 후 공격을 실행한다.
돌렸더니 한 값만 length가 다른 payload가 있었다.

이것이 로컬호스트의 포트번호임을 예측할 수 있다.
http://2130706433:1532/flag.txt 를 url에 입력해준다.

입력했더니 이미지 모양 이모지가 나타났고, 이를 개발자도구를 통해 분석해보았다.

이모지에 base64값이 주어져있음을 확인했고, 이 줄을 복사해 디코더에 붙여넣어보았다.
플래그를 획득할 수 있었다!

