설명
서버사이드 요청 위조(SSRF)는 공격자가 서버를 통해 임의의 요청을 보내도록 유도하는 취약성입니다. 이를 통해 내부 네트워크에 접근하거나 민감한 데이터를 탈취할 수 있습니다. SSRF는 서버가 사용자의 입력을 기반으로 의도하지 않은 서버에 요청을 보낼 때 발생합니다.
잠재적 영향
- 내부 네트워크 접근: 공격자가 서버를 통해 내부 네트워크에 접근하여 민감한 정보를 얻을 수 있습니다.
- 데이터 유출: 공격자가 내부 서버의 데이터를 탈취할 수 있습니다.
- 서비스 중단: 악의적인 요청으로 인해 서비스가 중단될 수 있습니다.
해결 방법
- 입력값 검증: 외부 요청을 보내기 전에 입력값을 철저히 검증합니다.
- 고정 매핑 사용: 가능하면 사용자에게 URL을 받지 말고, 서버가 소유한 식별자→고정 URL 매핑으로 대상을 선택합니다.
- URL 입력이 불가피하면 표준 파서로 스킴과 호스트를 분리한 뒤 서버 소유 허용 목록과 정확히 비교합니다.
- 추가 방어: 자동 리다이렉트를 끄거나 목적지를 매번 재검증하고, DNS 해석 결과의 사설 IP/링크로컬 주소와 예기치 않은 프록시 사용을 차단합니다.
예시
변경 전
python
# Unsafe SSRF in Flask
from flask import Flask, request
import requests
app = Flask(__name__)
@app.route('/fetch')
def fetch():
url = request.args.get('url')
response = requests.get(url)
return response.content
변경 후
python
# Safe SSRF mitigation in Flask
from flask import Flask, request, abort, Response
import requests
app = Flask(__name__)
@app.route('/fetch')
def fetch():
target_id = request.args.get('target')
# 사용자는 식별자만 선택하고, 실제 URL은 고정 분기에서 대입한다.
if target_id == "status":
url = "https://status.example.com/health"
elif target_id == "catalog":
url = "https://api.example.com/v1/catalog"
else:
abort(400)
max_response_bytes = 1024 * 1024
chunks = []
total = 0
with requests.get(
url, timeout=3, allow_redirects=False, stream=True
) as response:
for chunk in response.iter_content(chunk_size=64 * 1024):
total += len(chunk)
if total > max_response_bytes:
abort(502)
chunks.append(chunk)
status_code = response.status_code
proxied = Response(
b''.join(chunks),
status=status_code,
content_type="application/octet-stream",
)
proxied.headers["X-Content-Type-Options"] = "nosniff"
return proxied
설명:
- 변경 전: 사용자 입력을 검증하지 않고 외부 요청을 보내 SSRF 공격에 취약합니다.
- 변경 후: 사용자가 제공한 URL 대신 허용된 식별자별로 서버 소유 URL만 선택하고, 다른 값은 거부합니다. 자동 리다이렉트를 끄고 연결·읽기 대기 제한과 1 MiB 응답 제한을 적용하며, 콘텐츠 타입과
nosniff를 고정합니다.timeout=3은 전체 작업의 3초 기한이 아니므로 필요한 전체 처리 기한과 네트워크 송신 정책은 별도로 구성하세요.