사전 동일 출처 정책
개념

동일 출처 정책

gabury1고친 사람 github-actions[bot]

동일 출처 정책은 브라우저가 한 웹사이트의 코드를 다른 웹사이트의 데이터에서 떼어 놓는 규칙입니다. 주소가 조금이라도 다르면 남남으로 보고 그 사이에 칸막이를 칩니다. 그래서 아무 페이지나 열어 두어도 그 페이지가 다른 탭에 열린 내 계좌 화면을 훔쳐보지 못합니다.

쉽고 빠른 이해

브라우저는 한 페이지의 스크립트가 다른 사이트에서 받아 온 내용을 읽지 못하게 막습니다. 어쩌다 열어 둔 광고 페이지가 같은 브라우저에 로그인해 둔 메일함을 긁어 가지 못하는 것이 이 덕분입니다.

막지 않으면 브라우저에 쌓인 로그인 상태가 모든 사이트의 공용이 됩니다. 아무 사이트나 내 이름으로 요청을 보내고 그 답까지 읽어 갈 수 있습니다.

어떻게 도나:

  1. 브라우저가 주소에서 통신 방식·호스트 이름·포트 번호를 뽑아 그 자료의 출처로 삼습니다
  2. 읽으려는 쪽의 출처와 읽히는 쪽의 출처를 견줍니다
  3. 셋이 모두 같을 때만 읽기를 허락합니다

대가도 있습니다. 내 서비스를 여러 도메인에 나눠 놓으면 내 코드끼리도 막힙니다. 그래서 서버가 허락을 따로 내주는 장치를 덧대야 합니다.

상세

동전을 넣고 쓰는 물품 보관함을 떠올려 봅니다. 보관함은 앞에 선 사람이 누구인지 보지 않고 번호가 맞는 열쇠가 꽂혔는지만 봅니다. 번호가 하나 어긋나면 제 짐을 맡긴 사람도 문을 못 열고, 남이 주워 온 열쇠라도 번호만 맞으면 그대로 열립니다.

동일 출처 정책은 브라우저 안에서 서로 다른 출처의 자료가 섞이지 않게 막는 규칙입니다. 여기서 출처는 자료를 받아 온 주소에서 몇 조각을 잘라 만든 이름표를 말합니다. 브라우저는 읽으려는 쪽과 읽히는 쪽의 이름표를 견주어 같을 때만 읽기를 허락합니다.

출처는 주소의 세 조각이다

출처를 이루는 조각은 셋입니다. 통신 방식, 호스트 이름, 포트 번호입니다.

통신 방식은 주소 맨 앞의 http 나 https 처럼 어떻게 주고받을지를 정하는 이름입니다. 호스트 이름은 그 뒤에 오는 기계 이름이고, 포트 번호는 그 기계의 어느 문으로 들어갈지 정하는 숫자입니다.

https://shop.example.com/cart 를 보고 있다고 할 때, 브라우저는 다른 주소들을 이렇게 가릅니다.

견주는 주소 다른 조각 같은 출처인가
https://shop.example.com/order 없다 ✓
http://shop.example.com/cart 통신 방식 ✗
https://api.example.com/cart 호스트 이름 ✗
https://shop.example.com:8443/cart 포트 번호 ✗

표의 첫 줄이 말하는 것은 경로가 출처에 안 들어간다는 뜻입니다. 같은 기계의 같은 문으로 들어가면 어느 페이지든 한 식구입니다.

반대로 하위 도메인 하나만 달라도 남남입니다. shop.example.com 과 api.example.com 은 회사가 같아도 브라우저에게는 다른 출처입니다.

아래 그림은 이 판정이 어떤 차례로 도는지 보입니다. 어느 한 조각이라도 어긋나면 거기서 끝납니다.

flowchart TD
    A["읽으려는 자료의 주소"] --> B{"통신 방식이 같나"}
    B -->|다르다| X["다른 출처 · 읽기를 막는다"]
    B -->|같다| C{"호스트 이름이 같나"}
    C -->|다르다| X
    C -->|같다| D{"포트 번호가 같나"}
    D -->|다르다| X
    D -->|같다| Y["같은 출처 · 읽기를 허락한다"]

막는 것은 읽기이지 보내기가 아니다

브라우저는 다른 출처로 요청이 나가는 것 자체는 대개 막지 않습니다. 요청은 나가고 서버도 그 요청을 처리합니다.

막히는 것은 그다음입니다. 돌아온 응답을 페이지의 스크립트가 읽으려 할 때 브라우저가 가로막고 오류를 냅니다.

JavaScript
fetch("/account")            // 읽힌다
fetch("https://b.example/")  // 못 읽는다

두 줄의 차이는 응답을 손에 쥐느냐뿐입니다. 아래 줄도 요청은 이미 나간 뒤입니다.

이 차이가 중요한 까닭은 보내는 것만으로 끝나는 일이 많기 때문입니다. 남의 사이트에 「이체하라」는 요청을 보내면 답을 못 읽어도 이체는 일어납니다. 이 틈을 노리는 공격이 크로스 사이트 요청 위조이고, 그래서 동일 출처 정책만으로는 그 공격을 막을 수 없습니다.

그림으로 보면 브라우저가 어느 대목에서 끼어드는지 또렷해집니다.

sequenceDiagram
    participant 스크립트 as a.example 의 스크립트
    participant 브라우저
    participant 서버 as b.example 서버
    스크립트->>브라우저: b.example 의 자료를 달라
    브라우저->>서버: 요청을 보낸다
    서버-->>브라우저: 응답을 돌려준다
    Note over 브라우저: 출처가 다르다고 판정한다
    브라우저-->>스크립트: 오류만 준다

창과 문서 사이에도 칸막이가 있다

읽기를 막는 대상은 서버에서 받아 온 응답만이 아닙니다. 한 페이지가 다른 페이지를 품거나 새 창으로 열었을 때, 그 안쪽 문서를 들여다보는 것도 같은 잣대로 막습니다.

브라우저는 문서를 프로그램이 다룰 수 있게 나무 모양으로 펼쳐 놓습니다. 이 펼쳐 놓은 구조를 DOM(Document Object Model, 문서 객체 모델)이라고 부릅니다. 스크립트가 화면의 글자나 입력값을 읽는다는 것은 이 구조를 타고 들어간다는 뜻입니다.

페이지 안에 다른 페이지를 끼워 넣는 틀을 iframe이라고 합니다. 광고나 결제 화면이 흔히 이 틀로 들어옵니다. 바깥 페이지의 스크립트는 틀 안이 같은 출처일 때만 그 안의 구조를 읽을 수 있습니다.

저장소는 잣대가 조금씩 다르다

브라우저에 저장되는 값도 출처별로 칸이 갈립니다. 웹 스토리지나 IndexedDB에 넣은 값은 넣은 출처에서만 다시 읽힙니다.

쿠키는 이 셋보다 먼저 만들어져서 잣대가 어긋납니다. 쿠키는 통신 방식과 포트 번호를 따지지 않고 도메인과 경로로 범위를 정합니다.

그래서 같은 도메인의 다른 문에서 도는 서비스끼리 쿠키가 섞입니다. 출처는 다른데 쿠키는 공유되는 셈입니다. 이 어긋남 때문에 쿠키 쪽에는 SameSite 쿠키처럼 범위를 따로 좁히는 장치가 나중에 덧붙었습니다.

막힌 길을 여는 세 수단

내 서비스를 여러 도메인에 나눠 놓으면 내 코드끼리도 막힙니다. 그래서 칸막이를 정해진 방식으로 여는 길이 마련돼 있습니다.

그중 하나가 CORS(Cross-Origin Resource Sharing, 교차 출처 자원 공유)입니다. 자료를 내주는 서버가 응답 머리말에 「이 출처는 읽어도 된다」고 적어 두면 브라우저가 그 출처에만 칸막이를 내립니다.

여는 수단 누가 허락하나 언제 쓰나
CORS 자료를 내주는 서버 다른 도메인의 서버를 스크립트로 부를 때
postMessage 메시지를 받는 창 틀 안팎이나 창끼리 값을 주고받을 때
리버스 프록시 앞단에 선 서버 브라우저에 한 도메인으로만 보이게 할 때

표의 가운데 칸이 이 수단들의 공통점입니다. 브라우저가 스스로 판단해 여는 것이 하나도 없습니다.

자료를 내주는 쪽이나 메시지를 받는 쪽이 먼저 허락을 밝혀야 칸막이가 내려갑니다. 그래서 허락 목록을 아무 출처나 받도록 넓혀 두면 정책이 있으나 마나 해집니다.

이 정책이 막지 못하는 것

동일 출처 정책은 출처가 다른 두 쪽 사이만 봅니다. 공격이 그 선 안쪽에서 일어나면 소용이 없습니다.

교차 사이트 스크립팅이 그렇습니다. 공격자가 심은 스크립트가 피해자 사이트 안에서 돌면 브라우저에게는 그냥 그 사이트 자신의 코드입니다. 같은 출처이니 막을 까닭이 없습니다.

앞서 본 크로스 사이트 요청 위조도 마찬가지입니다. 보내기는 막지 않는다는 규칙의 틈을 쓰는 공격이라 이 정책의 바깥에 있습니다.

그래서 브라우저 보안은 이 정책 하나에 기대지 않습니다. 실을 수 있는 스크립트의 출처를 따로 제한하는 콘텐츠 보안 정책, 쿠키가 따라붙는 조건을 좁히는 SameSite 표시, 탭마다 처리 과정을 갈라 두는 사이트 격리가 겹겹이 얹힙니다.

관련 항목

출처를 이루는 주소 조각

URL · 스킴 · 호스트 · 포트 · 도메인 · 서브도메인

이 정책이 통제하는 브라우저 기능

DOM · iframe · fetch · XMLHttpRequest · 캔버스 · WebGL

출처마다 칸이 갈리는 브라우저 저장소

쿠키 · 웹 스토리지 · IndexedDB · 캐시 스토리지

칸막이를 여는 규약과 수단

CORS · 프리플라이트 요청 · Access-Control-Allow-Origin · postMessage · JSONP · 리버스 프록시

이 칸막이를 우회하려는 공격

교차 사이트 스크립팅 · 크로스 사이트 요청 위조 · 클릭재킹 · DNS 리바인딩 · 세션 하이재킹

브라우저를 함께 지키는 다른 보안 장치

콘텐츠 보안 정책 · SameSite 쿠키 · HttpOnly · 사이트 격리 · 샌드박스 · HTTPS

이 정책을 직접 집행하는 프로그램

브라우저 · 렌더링 엔진 · 자바스크립트 엔진

이 정책이 지키려는 보안 성질

기밀성 · 무결성 · 격리 · 최소 권한

다른 이름: same-origin policy · Same-Origin Policy · SOP · 동일 출처 원칙 · 같은 출처 정책