1. Server-side Request Forgery(SSRF)
Server-side Request Forgery(SSRF)는 웹 서비스의 요청을 변조하는 취약점.
최근의 대다수 서비스들은 마이크로서비스로 구조를 많이 바꾸고, 새롭게 개발하는 추세이기 때문에 SSRF 취약점의 파급력이 더욱 높아지고 있다.
마이크로서비스란?
마이크로서비스는 소프트웨어가 잘 정의된 API를 통해 통신하는 소규모의 독립적인 서비스로 구성되어 있는 경우의 소프트웨어 개발을 위한 아키텍처 및 조직적 접근 방식. 이러한 서비스는 독립적인 소규모 팀에서 보유한다.
만약 공격자가 SSRF 취약점을 통해 웹 서비스의 권한으로 요청을 보낼 수 있다면 공격자는 외부에서 간접적으로 내부망 서비스를 이용할 수 있고, 이는 곧 기업에 막대한 피해를 입힐 수 있다.
웹 서비스가 보내는 요청을 변조하기 위해서는 요청 내에 이용자의 입력값이 포함돼야 한다.
SSRF를 예방하기 위해서는 입력 값에 대한 적절한 필터링과 도메인 또는 아이피에 대한 검증이 필수.
2. 취약점이 발생하는 경우
1) 이용자가 입력한 URL에 요청을 보내는 경우
이용자가 웹 서비스에서 사용하는 마이크로서비스의 API 주소를 알아내고, image_url에 주소를 전달하면 외부에서 직접 접근할 수 없는 마이크로서비스의 기능을 임의로 사용할 수 있다.
image_downloader 엔드포인트의 image_url에 request_info 엔드포인트 경로를 입력해보자.
<http://127.0.0.1:8000/image_downloader?image_url=http://127.0.0.1:8000/request_info>
위 경로에 접속하면 image_downloader에서는 http://127.0.0.1:8000/request_info URL에 HTTP 요청을 보내고 응답을 반환한다.
반환한 값을 확인해보면 브라우저로 request_info 엔드포인트에 접속했을 때와 다르게 브라우저 정보가 python-requests/<LIBRARY_VERSION>인 것을 확인 가능
2) 웹 서비스의 요청 URL에 이용자의 입력값이 포함되는 경우
웹 서비스가 요청하는 URL에 이용자의 입력값이 포함되면 요청을 변조할 수 있다.
이용자의 입력값 중 URL의 구성 요소 문자를 삽입하면 API 경로를 조작할 수 있다.
예를 들어, 예시 코드의 user_info ****함수에서 user_idx에 ../search를 입력할 경우 웹 서비스는 다음과 같은 URL에 요청을 보낸다.
예시 코드
INTERNAL_API = "<http://api.internal/>"
# INTERNAL_API = "<http://172.17.0.3/>"
@app.route("/v1/api/user/information")
def user_info():
user_idx = request.args.get("user_idx", "")
response = requests.get(f"{INTERNAL_API}/user/{user_idx}")
@app.route("/v1/api/user/search")
def user_search():
user_name = request.args.get("user_name", "")
user_type = "public"
response = requests.get(f"{INTERNAL_API}/user/search?user_name={user_name}&user_type={user_type}")
<http://api.internal/search>
`..`는 상위 경로로 이동하기 위한 구분자로, 해당 문자로 요청을 보내는 경로를 조작할 수 있다.
해당 취약점은 경로를 변조한다는 의미에서 Path Traversal이라고 불린다.
이 외에도, #` 문자를 입력해 경로를 조작할 수 있다.
`#` 문자는 Fragment Identifier 구분자로, 뒤에 붙는 문자열은 API 경로에서 생략된다.
예를 들어, user_search 함수에서 user_name에 secret&user_type=private#를 입력할 경우 웹 서비스는 다음과 같은 URL에 요청을 보낸다.
<http://api.internal/search?user_name=secret&user_type=private#&user_type=public>
따라서 해당 URL은 실제로 아래와 같은 URL을 나타낸다.
<http://api.internal/search?user_name=secret&user_type=private>
3) 웹 서비스의 요청 Body에 이용자의 입력값이 포함되는 경우
예시 코드 ) 내부 API로 요청을 보내기 전에 데이터를 구성하는 것을 확인할 수 있다.
data = f"title={title}&body={body}&user={session['idx']}
데이터를 구성할 때 이용자의 입력값인 title, body 그리고 user의 값을 파라미터 형식으로 설정한다.
이로 인해 이용자가 URL에서 파라미터를 구분하기 위해 사용하는 구분 문자인 `&`를 포함하면 설정되는 data의 값을 변조할 수 있다. title에서 title&user=admin를 삽입하면 다음과 같이 data가 구성된다.
title=title&user=admin&body=body&user=guest
이용자가 & 구분자를 포함해 user 파라미터를 추가했다. 내부 API에서는 전달받은 값을 파싱할 때 앞에 존재하는 파라미터의 값을 가져와 사용하기 때문에 user의 값을 변조할 수 있다.
title&user=admin를 삽입했을 때의 실행 결과를 확인해보면 user가 "admin"으로 변조된 것을 확인할 수 있다.
{ "body": "body", "title": "title", "user": "admin" }
Reference
드림핵 STAGE9 Server Side Request Forgery