CORS 자격 증명 null Origin 허용

CORS Misconfiguration (Allow-Credentials with null Origin)

설명

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을 추가해 캐시 오작동을 방지합니다.

참조