서버측 요청 위조

Server-Side Request Forgery (SSRF)

설명

서버사이드 요청 위조(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초 기한이 아니므로 필요한 전체 처리 기한과 네트워크 송신 정책은 별도로 구성하세요.

참조