설명
Access-Control-Allow-Credentials: true와 함께 Access-Control-Allow-Origin: null을 반환하면, null로 직렬화되는 불투명한 출처에도 응답 열람을 허용할 수 있습니다. 공격자는 sandbox iframe이나 data: URL 등으로 이런 출처를 만들 수 있습니다. 클라이언트의 자격 증명 옵션과 쿠키 정책이 전송을 허용하면 인증된 응답의 민감 정보를 읽을 수 있습니다.
CORS 헤더는 응답 열람을 허용하는 조건입니다. 자격 증명을 자동으로 만들거나 모든 교차 출처 요청을 차단하는 서버 접근 제어는 아닙니다.
잠재적 영향
- 인증된 데이터 유출: 공격자가 피해자의 쿠키가 포함된 요청 응답을 읽어 계정 정보, 개인 데이터, CSRF 토큰 등 민감 정보를 탈취할 수 있습니다.
- 접근 통제 우회: Origin 기반 정책을 믿는 백엔드가 'null'을 신뢰하면 내부 API나 관리자 엔드포인트의 보호가 약화됩니다.
- CSRF 영향 증대: 본래 응답을 읽지 못하는 CSRF 시나리오에서, CORS 오설정으로 응답 열람까지 가능해지면 공격 자동화 및 추가 공격(권한 확인 우회)에 악용될 수 있습니다.
해결 방법
- 민감한 응답에 'null' Origin을 허용하지 마세요. 자격 증명 요청에는 구체적인 허용 출처를 반환하세요. '*'은 자격 증명이 포함된 응답의 열람을 허용하는 대안이 아닙니다.
- 엄격한 Allowlist 적용: 신뢰 가능한 구체적 Origin 목록과 일치할 때만 Access-Control-Allow-Origin을 그 Origin 값으로 설정하고, Vary: Origin을 함께 반환하세요.
- 미일치 Origin 처리: 일치하지 않으면 Access-Control-Allow-Origin/Allow-Credentials를 설정하지 말고, 사전 요청(OPTIONS)은 403으로 거부하세요.
- 최소 권한 원칙: 정말 필요한 경로와 출처에만 자격 증명 응답을 허용하고, 서버의 인증·인가와 CSRF 방어를 별도로 적용하세요.
- 방어 강화: SameSite로 교차 사이트 쿠키 전송을 제한하고 Secure와 HttpOnly로 전송 경로와 스크립트 접근을 보호하세요.
예시
헤더 설정과 /me 경로의 프리플라이트 예시입니다. 실제 인증·인가와 CSRF 처리는 생략했습니다.
변경 전
javascript
const express = require("express");
const app = express();
// 취약: null Origin을 허용하면서 Credentials를 켬
app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "null"); // BAD
res.header("Access-Control-Allow-Credentials", "true"); // BAD
next();
});
app.get("/me", (req, res) => {
res.json({ user: "alice", email: "alice@example.com" });
});
변경 후
javascript
const express = require("express");
const app = express();
const ALLOWLIST = new Set([
"https://app.example.com",
"https://admin.example.com",
]);
app.use((req, res, next) => {
const origin = req.headers.origin;
if (origin && ALLOWLIST.has(origin)) {
res.setHeader("Access-Control-Allow-Origin", origin);
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setHeader("Vary", "Origin");
} else {
// 허용되지 않은 Origin: CORS 헤더 미설정 (Credentials 미허용)
res.removeHeader("Access-Control-Allow-Credentials");
}
next();
});
// Preflight 처리 예시
app.options("/me", (req, res) => {
const origin = req.headers.origin;
if (origin && ALLOWLIST.has(origin)) {
res.set({
"Access-Control-Allow-Origin": origin,
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Methods": "GET",
"Access-Control-Allow-Headers": "Content-Type",
Vary: "Origin",
});
return res.sendStatus(204);
}
return res.sendStatus(403);
});
app.get("/me", (req, res) => {
res.json({ user: "alice", email: "alice@example.com" });
});
설명:
- 변경 전: 'null' Origin은 sandbox iframe, data: URL 등에서 쉽게 만들어낼 수 있는 공격자 제어 Origin입니다. 이를 Access-Control-Allow-Origin으로 허용하고 Access-Control-Allow-Credentials를 true로 설정하면, 브라우저 설정과 쿠키 정책이 자격 증명 전송을 허용할 때 공격자 스크립트가 인증된 응답을 읽을 수 있습니다.
- 변경 후: 서버가 미리 정의한 Allowlist에 포함된 신뢰 가능한 Origin에 대해서만 Access-Control-Allow-Origin을 그 Origin으로 설정하고 Credentials를 허용합니다. 허용되지 않은 Origin에는 CORS 헤더를 설정하지 않아 브라우저가 응답을 노출하지 못하게 합니다. 또한 Vary: Origin을 추가해 캐시 오작동을 방지합니다.