Ray Book
웹 보안

웹 보안의 기본 원칙

브라우저의 보안 모델과 Same-Origin Policy, 왜 교차 출처 접근이 차단되는지 시각화합니다

browsersecuritysame-origin-policyorigin

왜 브라우저 보안이 필요한가

웹은 기본적으로 위험한 환경입니다. 사용자는 링크 하나로 어떤 페이지든 방문할 수 있고, 그 페이지는 JavaScript를 실행할 수 있습니다. 문제는 사용자가 여러 탭을 동시에 열어둔다는 점입니다. 한쪽 탭에는 은행 사이트가 로그인된 상태로 열려 있고, 다른 탭에서는 검색 결과로 찾은 낯선 사이트를 방문합니다.

만약 아무런 보안 규칙이 없다면 어떻게 될까요? 악성 사이트의 JavaScript가 은행 사이트의 DOM에 접근해서 계좌 잔액을 읽을 수 있습니다. 은행 API에 요청을 보내고 응답을 가로챌 수도 있습니다. 로그인 쿠키가 자동으로 전송되니까 서버 입장에서는 정상적인 사용자 요청과 구별할 수 없습니다.

이런 공격을 막기 위해 브라우저는 보안 모델을 내장하고 있습니다. 그 핵심이 바로 Same-Origin Policy (동일 출처 정책)입니다.

출처(Origin)란 무엇인가

브라우저가 "같은 출처인지 다른 출처인지"를 판단하는 기준은 단순합니다. URL에서 세 가지 요소를 비교합니다.

https://my-app.com:443/page
|       |              |
프로토콜     호스트      포트

프로토콜(http/https), 호스트(도메인), 포트 -- 이 세 가지가 모두 일치하면 같은 출처입니다. 하나라도 다르면 다른 출처로 판단합니다. 경로(/page)는 출처 판단에 포함되지 않습니다.

아래 시각화에서 다양한 시나리오를 단계별로 확인해 보세요. 어떤 경우에 같은 출처이고, 어떤 경우에 차단되는지 직관적으로 이해할 수 있습니다.

같은 출처
페이지 출처https://my-app.com
요청 대상https://my-app.com/api/data
fetch, DOM 접근, 쿠키 읽기
프로토콜(https), 호스트(my-app.com), 포트(443) 가 모두 같으면 같은 출처입니다. 모든 접근이 허용됩니다.

눈여겨볼 점이 있습니다. 서브도메인이 다르면 같은 출처가 아닙니다. app.my-site.comapi.my-site.com은 같은 회사가 운영하는 서비스라 하더라도 브라우저 입장에서는 다른 출처입니다. 이 경우 CORS 설정이 필요합니다. CORS의 동작 원리는 이전 글에서 자세히 다뤘습니다.

Same-Origin Policy

Same-Origin Policy(SOP)는 한 출처에서 로드된 문서나 스크립트가 다른 출처의 리소스와 상호작용하는 것을 제한하는 브라우저의 보안 메커니즘입니다. 1996년 Netscape Navigator 2.0에서 처음 도입된 이래 모든 브라우저가 구현하고 있습니다.

SOP가 차단하는 것들을 구체적으로 살펴봅시다.

DOM 접근 차단 -- iframe으로 다른 출처의 페이지를 임베드하더라도, JavaScript에서 그 iframe의 DOM에 접근할 수 없습니다.

// https://evil.com 에서 실행
const frame = document.getElementById("bank-frame");
frame.contentDocument; // SecurityError: Blocked a frame

네트워크 응답 읽기 차단 -- fetchXMLHttpRequest로 다른 출처에 요청을 보낼 수는 있지만, 응답을 JavaScript에서 읽을 수 없습니다. 단순 요청(simple request)의 경우 요청 자체는 서버에 도달하지만, 브라우저가 응답을 JavaScript에 전달하지 않습니다. 커스텀 헤더나 PUT/DELETE 같은 메서드를 사용하면 브라우저가 먼저 preflight(OPTIONS) 요청을 보내고, 서버가 허용하지 않으면 실제 요청 자체가 전송되지 않습니다.

// https://evil.com 에서 실행
fetch("https://bank.com/api/balance")
  .then((res) => res.json()) // CORS 에러로 차단
  .catch(console.error);

쿠키와 스토리지 격리 -- 각 출처는 자신만의 쿠키, localStorage, sessionStorage, IndexedDB를 가집니다. 다른 출처의 저장소에 접근할 수 없습니다.

SOP가 허용하는 것들

SOP는 모든 교차 출처 접근을 차단하지 않습니다. 임베딩은 대부분 허용하고, 읽기 를 차단합니다. 이 구분이 중요합니다.

허용되는 교차 출처 임베딩은 다음과 같습니다.

<!-- JavaScript 로드, 허용 -->
<script src="https://cdn.example.com/lib.js"></script>

<!-- CSS 로드, 허용 -->
<link rel="stylesheet" href="https://cdn.example.com/style.css" />

<!-- 이미지 로드, 허용 -->
<img src="https://cdn.example.com/photo.jpg" />

<!-- 폼 제출, 허용 (응답을 읽을 수 없을 뿐) -->
<form action="https://other-site.com/submit" method="POST">

이것이 CDN이 동작하는 이유입니다. 다른 출처의 스크립트, 스타일시트, 이미지를 HTML 태그로 로드하는 것은 허용됩니다. 하지만 JavaScript에서 그 내용을 프로그래밍적으로 읽는 것은 차단됩니다.

예를 들어, <img> 태그로 교차 출처 이미지를 표시할 수 있지만, 그 이미지를 <canvas>에 그린 뒤 getImageData()로 픽셀 데이터를 읽으려 하면 차단됩니다. 이를 tainted canvas 라고 합니다.

<form action>으로 다른 출처에 데이터를 전송하는 것도 허용됩니다. 이것이 가능한 이유는 폼 제출 후 페이지가 전환되면서 기존 페이지의 JavaScript가 응답을 읽을 수 없기 때문입니다. 하지만 이 특성이 CSRF 공격의 원인이 되기도 합니다.

보안 위협 개요

SOP가 기본적인 보호를 제공하지만, 웹 보안 위협은 그보다 다양합니다. 이 시리즈에서 다룰 주요 위협을 간단히 소개합니다.

XSS (Cross-Site Scripting) -- 공격자가 신뢰할 수 있는 사이트에 악성 스크립트를 주입하는 공격입니다. 주입된 스크립트는 해당 사이트의 출처에서 실행되므로 SOP의 보호 범위 밖에서 동작합니다. 같은 출처에서 실행되므로 SOP가 이를 차단하지 못합니다. 사용자의 쿠키 탈취, 키 입력 기록, 페이지 변조가 가능합니다.

CSRF (Cross-Site Request Forgery) -- 사용자가 로그인된 상태를 이용해 악성 사이트에서 정상 사이트로 위조된 요청을 보내는 공격입니다. <form> 제출이 교차 출처에서 허용되고 쿠키가 자동 전송되는 특성을 악용합니다.

클릭재킹 (Clickjacking) -- 투명한 iframe으로 다른 사이트를 겹쳐 놓고, 사용자가 의도하지 않은 클릭을 유도하는 공격입니다. 사용자는 보이는 버튼을 클릭한다고 생각하지만, 실제로는 숨겨진 iframe의 버튼을 클릭하게 됩니다.

각 공격의 원리와 방어 방법은 이 시리즈의 다음 글들에서 상세히 다룹니다.

브라우저의 다층 방어

현대 브라우저는 SOP 하나에만 의존하지 않습니다. 여러 계층의 보안 메커니즘이 서로 다른 위협을 막습니다.

계층무엇을 막는가
Same-Origin Policy다른 출처의 DOM/데이터 읽기
CORSSOP를 안전하게 완화 -- 서버가 명시적으로 허용한 출처만 접근 가능
CSP (Content Security Policy)XSS -- 실행 가능한 스크립트의 출처를 제한
SameSite 쿠키CSRF -- 교차 사이트 요청에 쿠키 전송을 제한
HTTPS중간자 공격 -- 전송 중 데이터 변조와 도청을 방지
X-Frame-Options / frame-ancestors클릭재킹 -- 페이지가 iframe에 임베드되는 것을 제한

각 계층은 독립적으로 동작합니다. SOP가 있어도 XSS로 코드가 주입되면 같은 출처에서 실행되므로 SOP를 우회합니다. 그래서 CSP가 필요합니다. CORS가 있어도 쿠키가 자동 전송되면 CSRF가 가능합니다. 그래서 SameSite 쿠키가 필요합니다.

보안은 하나의 방어벽이 아니라 여러 계층의 조합입니다. 한 계층이 뚫리더라도 다른 계층이 피해를 줄여줍니다. 이것이 심층 방어 (Defense in Depth) 원칙입니다.

실무에서의 보안 점검

이론을 알았으니 실무에서 바로 확인할 수 있는 것들을 정리합니다.

브라우저 개발자 도구의 Application 탭에서 현재 페이지의 쿠키, localStorage, sessionStorage를 확인할 수 있습니다. 각 저장소가 출처별로 격리되어 있는 것을 직접 확인해 보세요.

Network 탭에서 교차 출처 요청을 찾아보면, 브라우저가 자동으로 추가한 Origin 헤더와 서버의 Access-Control-Allow-Origin 응답 헤더를 볼 수 있습니다.

Console 탭에서 교차 출처 iframe의 DOM에 접근을 시도하면 SecurityError가 발생하는 것도 확인할 수 있습니다. 이것이 SOP가 실시간으로 동작하는 모습입니다.

// 콘솔에서 직접 테스트
window.origin; // 현재 페이지의 출처 확인
new URL("https://example.com:8080/path").origin; // URL의 출처 파싱

다음 단계

이 글에서는 브라우저 보안의 기초인 출처 개념과 Same-Origin Policy를 살펴봤습니다. SOP가 무엇을 차단하고 무엇을 허용하는지, 그리고 SOP만으로는 부족한 이유를 확인했습니다.

다음 글에서는 가장 흔하고 위험한 웹 공격인 XSS(Cross-Site Scripting) 를 다룹니다. 공격자가 어떻게 신뢰할 수 있는 사이트에 스크립트를 주입하는지, 그리고 이를 어떻게 방어하는지 구체적인 코드와 함께 살펴봅니다.