flawopen.com/Incidents/capital-one-ssrf-imds-july-2019
Capital One SSRF: AWS IMDSv1 자격 증명 탈취
연방 형사 고소장은 잘못 구성된 WAF가 SSRF로 AWS IMDSv1에 접근하게 해 임시 역할 자격 증명과 약 1억 600만 명의 S3 데이터를 노출했다고 주장했습니다. 토큰을 필수화하고 역할 권한을 제한하세요.[10], [2]
DOJ 형사 고소장은 다음과 같은 경로를 주장했습니다: 말해 준 주소에서 서류를 가져오는 안내 직원을 떠올려 보세요. 요청 중계 기능 때문에 누군가 그 직원을 건물 안쪽 사무실로 보낼 수 있습니다. 그곳에서 직원은 워크로드용 임시 출입증을 받을 수 있으며, 출입증은 연결된 역할이 허용한 방만 열 수 있습니다. 설계자는 직원만 그 사무실에 갈 수 있다고 생각했지만, 안내 직원을 그곳으로 유도할 수 있었습니다.[10], [8]
핵심 개념 및 용어
SSRF- 서버 측 요청 위조(
CWE-918): 공격자가 고른 요청을 서버가 보내게 만들어, 그 요청이 서버의 네트워크 위치와 신뢰를 갖게 하는 것입니다.[9] 웹 애플리케이션 방화벽- 웹 애플리케이션 앞단의 필터입니다. 형사 고소장은 Capital One의 잘못 구성된 WAF가 내부 주소로 요청을 중계할 수 있었다고 주장했습니다.[10]
IMDSv1- IMDSv1이 활성화되어 있으면 메타데이터 요청에 토큰이 필요하지 않습니다. IAM 역할의 보안 자격 증명 경로는 해당 역할에 연결된 임시 자격 증명을 반환합니다.[7], [8]
IMDSv2- IMDSv2는 PUT 요청으로 얻은 세션 토큰을 사용합니다. 토큰이 없는 IMDSv1 요청을 거부하도록
HttpTokens=required를 설정하세요. 이는 단순 GET만 보낼 수 있는 많은 SSRF 중계를 막지만 SSRF 전체를 차단하지는 않습니다.[7], [6] IAM 역할 자격 증명- 인스턴스에 발급되는 임시 AWS 키입니다. 역할이 할 수 있는 일은 키를 가진 사람도 만료될 때까지 모두 할 수 있습니다.[8]
프레임워크 매핑
- 이 가이드에서 설명하는 약점의 CWE 참조: CWE-918
- D3-OTF 아웃바운드 트래픽 필터링 — 이 가이드의 아웃바운드 네트워크 제한은 서비스 또는 호스트가 접근할 수 있는 대상을 줄입니다.
사고 타임라인
외부 연구자가 Capital One의 책임 있는 공개 프로그램을 통해 노출된 데이터를 신고합니다.[2]
Capital One이 사고를 발표하고 FBI가 시애틀에서 용의자를 체포합니다.[1]
단계별 공격 실행 흐름
소스 코드 비교: 취약한 구현 vs 보안 패치
이 Flask 코드는 설명용이며 Capital One의 설정이 아닙니다. 호스트 허용 목록은 애플리케이션의 기본 제어입니다. 필수 egress 프록시는 연결 시 DNS 확인 결과 주소에 정책을 적용하고, 인스턴스 메타데이터를 포함한 사설 및 link-local 범위를 차단하며, 모든 요청과 재시도에서 같은 검사를 반복해야 합니다. 프록시 설정에서 실제 동작을 확인하세요. 리디렉션은 따라가지 않습니다. 애플리케이션에서 먼저 DNS를 확인하고 나중에 연결하면 DNS rebinding 경쟁 조건이 남습니다.[9], [5], [6]
import requests
from flask import Flask, request
app = Flask(__name__)
@app.get("/fetch")
def fetch():
# 서버는 호출자가 제공한 URL을 가져옵니다.
url = request.args["target"]
resp = requests.get(url, timeout=5)
return resp.text
from urllib.parse import urlsplit
import requests
from flask import Flask, abort, request
app = Flask(__name__)
ALLOWED_HOSTS = {"images.example-cdn.com", "api.partner.example"}
EGRESS_PROXY = "http://egress-proxy.internal:3128"
# DNS 확인 후 대상 정책을 적용하도록 이 프록시를 구성합니다.
@app.get("/fetch")
def fetch():
parts = urlsplit(request.args.get("target", ""))
if parts.scheme != "https" or parts.hostname not in ALLOWED_HOSTS:
abort(400, "destination not allowed")
if parts.port not in (None, 443) or parts.username or parts.password:
abort(400, "destination not allowed")
resp = requests.get(parts.geturl(), timeout=(2, 5), allow_redirects=False, proxies={"https": EGRESS_PROXY}, trust_env=False)
if resp.is_redirect:
abort(502, "upstream redirect refused")
return resp.text
엔지니어링 및 시스템 보안 강화 체크리스트
- ✓URL 요청은 egress 프록시를 통과시키고 연결 경계에서 호스트 허용 목록과 IP 범위 정책을 적용하세요. 검사한 주소에만 연결하고 재시도에도 정책을 적용하세요. 리디렉션을 끄거나 각 단계를 다시 확인하세요. 169.254.169.254 문자열만 차단해서는 충분하지 않습니다.[9], [4]
- ✓해당하는 모든 인스턴스에
HttpTokens=required를 설정하고 기존 인스턴스도 확인하세요. 계정 기본값은 각 인스턴스를 자동으로 갱신하지 않습니다. 워크로드의 네트워크 경로에 맞춰HttpPutResponseHopLimit을 선택하세요. AWS는 값 1이 컨테이너에 문제를 일으킬 수 있다고 경고합니다. 토큰 필수 설정은 IMDSv1 요청을 거부하며, hop limit은 별도의 네트워크 경로 설정이지 토큰 없는 GET을 막는 제어가 아닙니다.[6], [7] - ✓형사 고소장은 SSRF 경로가 인스턴스 역할을 노출했다고 주장합니다. 자격 증명이 노출되어도 무관한 S3에 접근할 수 없도록 역할을 워크로드에 필요한 작업과 리소스로 제한하세요.[10], [8]
- ✓FlawOpen 탐지 지침: 인스턴스 네트워크 밖에서 역할의 임시 자격 증명이 사용되면 경고를 발생시키고 워크로드 및 클라우드 감사 맥락과 함께 조사합니다.
- ✓코드 리뷰에서 사용자가 서버가 가져오는 URL에 영향을 줄 수 있는 경로는 SQL 쿼리에 영향을 주는 경로와 똑같이 다루고, 허용 목록 테스트를 요구합니다.[9], [5]
출처
- 미국 법무부 — 대형 금융회사 데이터 절도 혐의로 시애틀 IT 종사자 체포 (2019년 7월 29일)
- Capital One — 2019년 사이버보안 사고 안내
- OCC — 2019년 사고 관련 Capital One 민사 제재금 (2020년 8월)
- 클라우드 환경에서 SSRF가 위험한 이유
- fetch()가 SSRF를 일으킬 수 있을까?
- AWS — Configure the Instance Metadata Service options
- AWS — Use the Instance Metadata Service to access instance metadata
- AWS — Retrieve security credentials from instance metadata
- OWASP — Server Side Request Forgery Prevention Cheat Sheet
- DOJ — United States v. Paige Thompson, criminal complaint (29 July 2019)