본문 바로가기
문서CORS를 모두에게 열고 로그인 정보까지 허용해요

CORS를 모두에게 열고 로그인 정보까지 허용해요

"어느 사이트에서 불러도 좋다(*)"와 "쿠키도 같이 보내라(credentials: true)"를 동시에 켜면, 악성 사이트가 로그인한 방문자의 자격 그대로 당신 API를 부를 수 있어요.

한눈에
위험도
높음(High)
CWE
CWE-942 · 교차출처 권한 과다 허용
OWASP
A05:2021
vg-cors-wildcard-credentials · vg-cors-reflect-origin

이게 무슨 말이냐 (쉽게)

브라우저에는 원래 "A사이트의 자바스크립트가 B사이트 데이터를 마음대로 읽지 못하게" 막는 담이 있습니다. CORS는 그 담에 당신이 직접 내주는 출입증이에요.

둘을 함께 켜면 "아무 사이트에서나, 우리 회원의 로그인 상태로" API를 부를 수 있게 됩니다. 브라우저 표준은 이 조합 자체는 거부하는데, 서버가 * 대신 요청이 보낸 Origin을 그대로 되돌려주면 실질적으로 같은 상태가 됩니다. 그게 두 번째 룰(vg-cors-reflect-origin)이고, 사실 *보다 위험해요 — 표준이 걸어둔 브레이크까지 풀리거든요.

실제로 어떻게 터지나 (시나리오)

당신 API는 쿠키로 로그인을 유지합니다. 개발 중 CORS 오류가 떠서 검색했고, 나온 답을 그대로 붙였어요.

res.setHeader("Access-Control-Allow-Origin", req.headers.origin);  // 요청 Origin을 반사
res.setHeader("Access-Control-Allow-Credentials", "true");

오류는 사라졌고, 그대로 배포했습니다. 공격자가 쿠폰 이벤트처럼 보이는 페이지를 만들어 링크를 뿌립니다. 당신 서비스에 로그인해 둔 사용자가 그 링크를 열면, 페이지의 스크립트가 조용히 fetch("https://내도메인/api/me", { credentials: "include" })를 실행해요. 서버는 그 Origin을 그대로 허가해 주고, 쿠키가 붙어 있으니 본인 확인까지 통과합니다. 이름·이메일·주소가 공격자 서버로 넘어갑니다. 사용자는 쿠폰 페이지를 봤을 뿐이에요.

최악의 경우

나한테 해당되나? (체크)

→ 2번이나 4번이 체크되면 해당됩니다.

어떻게 고치나 (복붙)

① 허용할 주소를 목록으로 못 박기 (와일드카드·반사 금지)

const ALLOWED = new Set([
  "https://내도메인.com",
  "https://app.내도메인.com",
]);

const origin = req.headers.get("origin") || "";
if (ALLOWED.has(origin)) {                       // 목록에 있을 때만 허가
  res.headers.set("Access-Control-Allow-Origin", origin);
  res.headers.set("Vary", "Origin");             // 캐시가 다른 사이트에 섞이지 않게
  res.headers.set("Access-Control-Allow-Credentials", "true");
}

② 라이브러리를 쓸 때도 목록으로

- app.use(cors({ origin: "*", credentials: true }));    // 전부 허용
- app.use(cors({ origin: true, credentials: true }));   // origin:true = 반사
+ app.use(cors({ origin: ["https://내도메인.com"], credentials: true }));

③ 공개 API라 정말 아무나 불러도 되면 — 자격증명을 끄기. Allow-Credentials를 아예 넣지 않고, 쿠키 대신 요청마다 토큰을 받으세요. * 자체가 문제가 아니라 * + 쿠키가 문제입니다.

④ 부분 일치 검사 금지 — origin.includes("내도메인.com")은 내도메인.com.공격자.kr을 통과시킵니다. 반드시 전체 문자열 일치로.

고친 뒤 확인

  1. 01

    남의 출처로 물어보기

    허가 헤더가 안 나와야 정상입니다.
    curl -sI https://내도메인/api/me -H "Origin: https://evil.example" | grep -i access-control

  2. 02

    내 도메인으로 물어보기

    Access-Control-Allow-Origin에 내 도메인 하나만 나와야 합니다(*가 아니라).

  3. 03

    캐시 오염 막기

    Vary: Origin이 붙었는지 확인 — 없으면 CDN이 A사이트용 응답을 B사이트에 그대로 줄 수 있습니다.

규제 연결

교차출처로 자격증명이 새는 구성이라 ISMS-P 2.6.1(접근통제)·2.10(보안관리), PIPA 제29조 안전조치의무에 걸립니다.

출처/더 읽기: OWASP A05:2021(Security Misconfiguration), CWE-942, MDN CORS 문서(Allow-Origin *와 credentials 조합 제한), PortSwigger Web Security Academy — CORS. 이 카드는 우리가 직접 작성했습니다.

문서 목록으로