postMessage 와일드카드 (origin) 사용

postMessage의 와일드카드 대상 출처로 인한 데이터 노출

설명

window.postMessage의 targetOrigin에 '*'를 지정하면 대상 창이나 iframe의 출처와 관계없이 그 창에 메시지를 보냅니다. 모든 창에 방송하는 것은 아니지만, 대상 창이 공격자 출처로 이동할 수 있다면 토큰이나 개인정보가 노출될 수 있습니다. 공격 가능성은 해당 창에 대한 참조, 이동 가능 여부와 실제로 보내는 데이터에 따라 달라집니다.

잠재적 영향

  • 민감정보 유출: '*'로 전송된 메시지를 악성 출처가 수신해 토큰, 세션 정보, 개인정보 등이 외부로 노출될 수 있습니다.
  • 계정 탈취/권한 남용: 인증 토큰이나 OAuth code 유출 시 공격자가 사용자로 가장하거나 API 권한을 남용할 수 있습니다.
  • 비즈니스 로직 악용: 신뢰된 창에만 전달해야 하는 명령/데이터가 제3자에게 전달되어 결제, 설정 변경 등 중요 기능이 오동작할 수 있습니다.

해결 방법

  • 민감한 메시지는 정확한 출처(스킴·호스트·포트)로만 보내세요. 예를 들어 https://example.com처럼 신뢰하는 구성에서 지정하세요.
  • 대상이 여러 개라면 허용 목록에 있는 출처만 선택하고, 일치하지 않을 때는 보내지 마세요. 조작 가능한 iframe.src에서 출처를 가져오는 것만으로는 충분하지 않습니다.
  • 수신 측에서 event.origin, 신뢰하는 창을 가리키는 event.source, 메시지의 타입과 필드를 검증하세요.
  • 민감정보 전송을 최소화하고, 필요한 일회성 토큰의 용도·수신자·만료 조건을 제한하세요.
  • iframe의 목적지를 신뢰하는 값으로 고정하고 필요한 sandbox 제한을 적용하세요. opener 통신이 필요 없는 새 창에는 noopener를 적용할 수 있습니다.

예시

변경 전

javascript
// sender (부모 페이지)
const iframe = document.getElementById("payFrame");
// 로컬에 저장된 인증 토큰을 그대로 전송 (민감정보)
const token = localStorage.getItem("auth_jwt");

// 취약: targetOrigin을 '*'로 사용 => 어떤 출처로 로드되었든 모두 수신 가능
iframe.contentWindow.postMessage({ type: "AUTH", token }, "*");

변경 후

javascript
// sender (부모 페이지)
const iframe = document.getElementById("payFrame");
// 신뢰하는 구성에서 대상 origin을 고정
const TARGET_ORIGIN = "https://pay.example.com";

// 민감정보는 보내지 않거나, 최소화/대체(예: 일회성 code)하여 전송
const message = { type: "REQUEST_PAYMENT", orderId: "ORD-2025-0912-001" };
iframe.contentWindow.postMessage(message, TARGET_ORIGIN);

// receiver (자식 iframe 페이지)
window.addEventListener("message", (event) => {
  // 수신 측 검증: 기대하는 부모 origin만 허용
  if (event.origin !== "https://app.example.com") return;
  if (event.source !== window.parent) return;
  // 메시지 형태/타입 검증
  if (!event.data || event.data.type !== "REQUEST_PAYMENT") return;
  if (typeof event.data.orderId !== "string") return;

  // 서버에서 주문 접근 권한과 결제 조건을 별도로 검증
  processPayment(event.data.orderId);
});

변경 전에는 대상 iframe이 공격자 출처로 이동하면 인증 토큰을 받을 수 있습니다. 변경 후에는 목적지 출처를 고정하고 수신 측에서 출처, 부모 창과 메시지 필드를 확인합니다. sender와 receiver는 서로 다른 페이지의 코드입니다. processPayment의 구현은 생략했으며, 서버에서 주문 접근 권한과 결제 조건을 확인해야 합니다.

참조