CORS 구성 오류는 버그 바운티 프로그램에서 가장 일관되게 보상받는 취약점 중 하나이며, 동시에 가장 과소평가된 취약점 중 하나입니다. 잘못 구성된 Access-Control-Allow-Origin 헤더 하나가 인증된 모든 API 엔드포인트를 공격자가 통제하는 도메인에 노출시킬 수 있습니다.
기본적으로 브라우저는 동일 출처 정책(Same-Origin Policy, SOP)을 적용합니다. attacker.com에서 실행되는 JavaScript는 bank.com의 응답을 읽을 수 없습니다. CORS는 이 제한을 완화하는 메커니즘으로, 잘못 구성되면 SOP를 완전히 무력화할 수 있습니다.
교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 서버가 자신의 출처 이외에 어떤 출처(도메인 + 스킴 + 포트)가 응답을 읽을 수 있는지 나타낼 수 있게 해주는 HTTP 헤더 기반 메커니즘입니다.
이해해야 할 핵심 헤더:
Access-Control-Allow-Origin — 응답을 읽을 수 있는 출처를 지정Access-Control-Allow-Credentials — 쿠키/인증 헤더 포함 여부Access-Control-Allow-Methods — 허용된 HTTP 메서드Access-Control-Allow-Headers — 허용된 요청 헤더가장 치명적인 취약점은 서버가 Access-Control-Allow-Origin: [공격자 통제 도메인]과 Access-Control-Allow-Credentials: true를 함께 반환할 때 발생합니다. 이는 공격자가 자신의 사이트에서 인증된 요청을 보내고 응답을 읽을 수 있음을 의미합니다.
# 취약한 서버측 로직 (Python/Flask)
@app.after_request
def add_cors(response):
origin = request.headers.get('Origin')
response.headers['Access-Control-Allow-Origin'] = origin # 절대 이렇게 하지 마세요
response.headers['Access-Control-Allow-Credentials'] = 'true'
return response
어떤 출처든 인증된 응답을 읽을 수 있게 됩니다. 이것은 버그 바운티에서 발견되는 가장 흔한 CORS 취약점입니다.
# 취약: 신뢰할 수 있는 도메인이 Origin 어디에든 포함되어 있는지 확인
if 'trusted-bank.com' in request.headers.get('Origin', ''):
# 우회: evil-trusted-bank.com 또는 trusted-bank.com.evil.com 등록
allow_origin(origin)
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
null 출처는 샌드박스 iframe, 리디렉션, file:// URL을 통해 트리거될 수 있습니다.
# 테스트 1: 기본 반영 확인
curl -s -I -H "Origin: https://evil.com" \
https://target.com/api/userinfo \
| grep -i "access-control"
# 테스트 2: Null origin
curl -s -I -H "Origin: null" \
https://target.com/api/userinfo \
| grep -i "access-control"
# 테스트 3: 서브도메인 우회
curl -s -I -H "Origin: https://target.com.evil.com" \
https://target.com/api/userinfo \
| grep -i "access-control"
| 응답 | 심각도 | 악용 가능? |
|---|---|---|
ACAO: * | 낮음-보통 | 자격 증명 없이만 가능 |
ACAO: [악성 출처]만 | 보통 | 자격 증명 없이 |
ACAO: [악성 출처] + ACAC: true | 치명적 | 예 — 전체 자격 증명 도용 |
ACAO: null + ACAC: true | 높음 | 샌드박스 iframe 통해 |
# Python/Flask - 안전한 구현
ALLOWED_ORIGINS = {
'https://app.company.com',
'https://admin.company.com',
'https://company.com'
}
@app.after_request
def add_cors_headers(response):
origin = request.headers.get('Origin')
if origin in ALLOWED_ORIGINS:
response.headers['Access-Control-Allow-Origin'] = origin
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Vary'] = 'Origin' # 캐싱을 위해 필수!
return response
*) 절대 사용 금지null origin 신뢰 금지Vary: Origin 헤더 포함KENSAI는 모든 API 엔드포인트에서 CORS 구성 오류, 반영된 origin 취약점, null-origin 신뢰 문제를 지속적으로 스캔합니다.
무료 스캔 시작 →보안은 선택이 아닙니다.
🗡️ KENSAI 팀