-
Dreamhack - web-ssrfwargame/Dreamhack 2024. 3. 26. 01:13
[LEVEL 2 - web-ssrf - web]
이 문제의 목표는 SSRF 취약점을 통해 플래그를 획득하는 것이다.
flask로 작성된 image viewer 서비스이며, 플래그는 /app/flag.txt에 있다고 한다.
우선 문제 파일을 다운로드 받고 app.py 코드를 살펴본다.
/img_viewer은 GET과 POST 요청을 처리한다.
- GET : img_viewer.html을 렌더링
- POST : 이용자가 입력한 url에 HTTP 요청을 보내고, 응답을 img_viewer.html의 인자로 하여 렌더링
img_viewer 함수를 보면 이용자가 POST로 전달한 url에 HTTP 요청을 보내고, 응답을 반환한다. 그런데 조건문에서 서버 주소에 "127.0.0.1", "localhost"이 포함된 URL로의 접근을 막는다. 즉, 이 url 필터링을 우회하면 SSRF를 통해 내부 HTTP 서버에 접근할 수 있다.
cf) URL 필터링
- URL 필터링 : URL에 포함된 문자열을 검사하여 부적절한 URL로의 접근을 막는 보호 기법. 제어 방식에 따라 크게 아래 두 가지로 나뉨.
- 블랙리스트 필터링 : URL에 포함되면 안되는 문자열로 블랙리스트를 만들고, 이를 악용하여 이용자의 접근을 제어함. 블랙리스트 필터링에는 항상 예외가 있을 수 있어서 유의해야 함.
- 화이트리스트 필터링 : 접근을 허용할 URL로 화이트리스트를 만들고, 이용자가 화이트리스트 외의 URL에 접근하려고 하면 차단함.
그리고 run_local_server 함수를 살펴보면, 파이썬의 기본 모듈인 http를 이용하여 127.0.0.1의 임의 포트에 HTTP 서버를 실행하는 것을 알 수 있다.
그리고 http.server.HTTPServer()의 두 번째 인자로 http.server.SimpleHTTPRequestHandler를 전달하면, 현재 디렉터리를 기준으로 URL이 가리키는 리소스를 반환하는 웹 서버가 생성된다.
여기서 호스트가 127.0.0.1 이므로, 외부에서 이 서버에 직접 접근하는 것을 불가하다.
위 내용을 참고하여, 아래와 같이 URL 필터링을 우회하면 된다.
- 127.0.0.1과 매핑된 도메인 이름 사용 : 임의의 도메인 이름 구매 후 DNS 서버 등록하면 원하는 IP 주소(127.0.0.1)와 연결 가능. 이후 등록한 이름이 IP 주소로 Resolve됨. 즉, 그 이름을 url로 사용하면 필터링 우회 가능. 이미 127.0.0.1에 매핑된 "*.vcap.me" 이용하는 방법도 있음.
- 127.0.0.1의 alias 이용 : 여러 방식으로 IP 표기 가능. ex) 127.0.0.1은 각 자릿수를 16진수로 변환한 0x7f.0x00.0x00.0x01 에서 .을 제거한 0x7f000001 또는 이를 10진수로 변환한 2130706433 또는 각 자리에서 0을 생략한 127.1 또는 127.0.1 등과 같은 호스트를 가리킴. 또한, 127.0.0.1부터 127.0.0.255까지의 IP는 루프백(loop-back) 주소라고 하여 모두 로컬 호스트를 가리킴.
- localhost의 alias 이용 : URL에서 호스트와 스키마는 대소문자 구분X. 즉, "localhost"의 임의 문자를 대문자로 바꿔도 같은 호스트임.
필터링을 우회할 수 있는 localhost URL
- http://vcap.me:8000/
- http://0x7f.0x00.0x00.0x01:8000/
- http://0x7f000001:8000/
- http://2130706433:8000/
- http://Localhost:8000/
- http://127.0.0.255:8000/
문제 환경에 접속하면 나오는 처음 화면이다.
로컬 호스트의 8000번 포트에는 문제 서버가 실행되고 있는데, 위와 같이 /img_viewer 페이지에서 http://Localhost:8000/ 을 쓰고 View를 누르면 문제 인덱스 페이지를 인코딩한 이미지가 반환된다.
즉, 앞서 정리해 본 필터링을 우회할 수 있는 localhost URL들은 로컬 호스트를 가리키면서, 필터링을 우회할 수 있는 URL이라는 것이 증명된 것이다.
개발자도구로 해당 코드 부분 더블클릭해서 자세히 보면 base64로 인코딩 된 인덱스 페이지의 소스 코드가 보인다.
이제 마지막으로 앞서 알게 된 url을 활용하여 랜덤한 포트를 찾는 파이썬 코드만 작성하면 무차별 대입 공격으로 포트를 찾을 수 있다.
아까 app.py에서 내부 HTTP 서버는 포트 번호가 1500 이상 1800 이하인 임의 포트에서 실행되고 있다는 걸 알 수 있었다.
문제 설명에 플래그는 "/app/flag.txt"에 있다고 했는데, 내부 HTTP 서버가 "/app"에서 실행되고 있으니까, 해당 서버의 /flag.txt를 읽으면 플래그값을 얻을 수 있다.
드림핵 강의 Exercise에 나와있는 것처럼 find_port.py 파일을 작성한다.
python find_port.py 15070 실행 후,
내부 포트 번호가 1537라는 것을 알아냈다.
위와 같이 /img_viewer 페이지에서 http://Localhost:1537/flag.txt 를 입력하고 View를 누른다.
그러면 깨진 이미지 파일이 하나 뜬다.
개발자 도구를 다시 열어서 Elements 탭에서 이미지 파일 부분을 보면 base64로 인코딩된 문자열이 있다.
<img src="data:image/png;base64, REh7NDNkZDIxODkwNTY0NzVhN2YzYmQxMTQ1NmExN2FkNzF9">
자세히 보면 위와 같다.
이제 뒷부분 REh7NDNkZDIxODkwNTY0NzVhN2YzYmQxMTQ1NmExN2FkNzF9 을 base64 디코딩하면 플래그값이 나올 것이다.
드림핵 툴즈 Cyber Chef 기능을 이용하여 Base64 디코딩하면 위와 같다.
플래그를 획득했다!
'wargame > Dreamhack' 카테고리의 다른 글
Dreamhack - BMP Recovery (1) 2024.05.01 Dreamhack - sleepingshark (0) 2024.05.01 Dreamhack - file-download-1 (1) 2024.03.25 Dreamhack - image-storage (0) 2024.03.18 Dreamhack - broken-png (0) 2024.03.18