CORS는 보안 장치다, 편의 장치가 아니다
개발 중 CORS 에러가 나면 대부분 Access-Control-Allow-Origin: *을 넣고 해결한다. AI 코딩 도구도 CORS 에러가 나면 가장 먼저 와일드카드를 제안한다. 문제는 이 설정이 프로덕션까지 올라간다는 것이다.
사례 1: 와일드카드 + credentials 조합
가장 위험한 패턴이다:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
이 조합은 어떤 사이트에서든 사용자의 인증 쿠키를 포함해서 API를 호출할 수 있다는 뜻이다. 공격자가 악성 사이트를 만들고, 피해자가 방문하면 로그인된 상태로 API를 호출해서 개인정보를 빼갈 수 있다.
다행히 대부분의 브라우저는 이 조합을 차단한다. 하지만 일부 구형 브라우저나 모바일 웹뷰에서는 통과되는 경우가 있다.
사례 2: Origin 헤더를 그대로 반사
와일드카드를 쓰면 credentials가 안 되니까, AI가 이런 코드를 만든다:
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header('Access-Control-Allow-Credentials', 'true');
next();
});
요청의 Origin을 그대로 응답에 넣는다. 결과적으로 모든 도메인을 허용하면서 credentials도 허용하는, 와일드카드보다 더 위험한 상태가 된다.
수정법: 허용할 도메인 목록을 배열로 관리하고, 목록에 있는 Origin만 허용한다:
const ALLOWED = ['https://myapp.com', 'https://admin.myapp.com'];
app.use((req, res, next) => {
const origin = req.headers.origin;
if (ALLOWED.includes(origin)) {
res.header('Access-Control-Allow-Origin', origin);
res.header('Access-Control-Allow-Credentials', 'true');
}
next();
});
사례 3: 서브도메인 패턴 매칭 실수
"*.myapp.com만 허용하겠다"는 의도로 이런 코드를 작성한다:
if (origin.endsWith('.myapp.com')) { allow(); }
문제는 evil-myapp.com도 .myapp.com으로 끝난다는 것이다. 공격자가 attacker-myapp.com 도메인을 등록하면 정책을 우회할 수 있다.
내 사이트 CORS 설정 확인
CORS 설정은 응답 헤더에서 확인할 수 있지만, 모든 엔드포인트를 하나씩 점검하기 어렵다. 자동 스캐너로 CORS를 포함한 보안 헤더 전체를 한 번에 점검할 수 있다.