[혼공] Same Origin Policy 정책 실습해보자

layl__a·2023년 4월 19일
0

CS 공부

목록 보기
2/3
post-thumbnail

공부하게 된 이유

dreamhack
에서 CS 기본 지식을 공부하게 되었다.

Same Origin Policy(SOP)란?


우리가 브라우저 요청을 보낼 때 해당 웹 서비스에서 사용하는 인증 정보인 쿠키를 HTTP 요청에 포함시켜 전달한다. 또한, 웹 리소스를 통해 간접적으로 타 사이트에 접근할 때도 인증 정보인 쿠키를 함께 전송하기도 한다.
이 특징 때문에 악의적인 이용자가 이 쿠키를 가로채어 이용자 권한을 이용해 서버에 HTTP 요청을 보내고, HTTP 응답 정보를 획득하는 코드를 실행할 수 있다. 이와 같은 문제를 방지하기 위해 데이터를 악의적인 페이지에서 읽 수 없도록 하는 동일 출처 정책, SOP 보안메커니즘이 탄생했다.

Same Origin Policy의 오리진(Origin) 구분 방법

구성 요소가 모두 일치해야 올바른 오리진이라고 한다.

  • 구성 요소 : 프로토콜(Protocol, Scheme), 포트(Port), 호스트(Host)

    예시 (https://same-origin.com/라는 오리진과 아래 URL을 비교했을 때 결과)

URL결과이유
https://same-origin.com/frame.htmlSame OriginPath만 다름
http://same-origin.com/frame.htmlCross OriginScheme이 다름
https://cross.same-origin.com/frame.htmlCross OriginHost가 다름
https://same-origin.com:1234/Cross OriginPort가 다름

SOP은 Cross Origin이 아닌 Same Origin일 때만 정보를 읽을 수 있도록 한다.

Same Origin Policy 실습

개발자 도구 F12를 누르고 콘솔 창에서 한 줄씩 직접 코드를 입력해보면서 실습해보자.

Same Origin

1. sameNewWindow = window.open('https://dreamhack.io/lecture');
2. console.log(sameNewWindow.location.href);
3. 결과: https://dreamhack.io/lecture

Cross Origin

1. crossNewWindow = window.open('https://theori.io');
2. console.log(crossNewWindow.location.href);
3. 결과: Origin 오류 발생

Cross Origin 데이터 읽기/쓰기

외부 출처에서 불러온 데이터를 읽으려고 할 때는 오류가 발생하지만, 데이터를 쓰는 것은 문제 없이 동작한다. 즉, 아래와 같은 코드는 오류가 발생하지 않는다.

1.crossNewWindow = window.open('https://theori.io');
2.crossNewWindow.location.href = "https://dreamhack.io";

SOP 데모

예시)

1. <!-- iframe 객체 생성 -->
2. <iframe src="" id="my-frame"></iframe>
3. 
4. <!-- Javascript 시작 -->
5. <script>
6. /* 2번째 줄의 iframe 객체를 myFrame 변수에 가져옵니다. */
7. let myFrame = document.getElementById('my-frame')
8. 
9.  /* iframe 객체에 주소가 로드되는 경우 아래와 같은 코드를 실행합니다. */
10. myFrame.onload = () => {
11.    /* try ... catch 는 에러를 처리하는 로직 입니다. */
12.     try {
13.        /* 로드가 완료되면, secret-element 객체의 내용을 콘솔에 출력합니다. */
14.        let secretValue = myFrame.contentWindow.document.getElementById('secret-element').innerText;
15.         console.log({ secretValue });
16.    } catch(error) {
17.        /* 오류 발생시 콘솔에 오류 로그를 출력합니다. */
18.        console.log({ error });
19.    }
20.}
21./* iframe객체에 Same Origin, Cross Origin 주소를 로드하는 함수 입니다. */
22. const loadSameOrigin = () => { myFrame.src = 'https://same-origin.com/frame.html'; }
23. const loadCrossOrigin = () => { myFrame.src = 'https://cross-origin.com/frame.html'; }
24. </script>
25.
26. <!--
27. 버튼 2개 생성 (Same Origin 버튼, Cross Origin 버튼)
28. -->
29. <button onclick=loadSameOrigin()>Same Origin</button><br>
30. <button onclick=loadCrossOrigin()>Cross Origin</button>
31.
32. <!--
33. frame.html의 코드가 아래와 같습니다.
34. secret-element라는 id를 가진 div 객체 안에 treasure라고 하는 비밀 값을 넣어두었습니다.
35. -->
36. <div id="secret-element">treasure</div>

출처 : https://learn.dreamhack.io/186#7

  • 코드 동작 설명
  1. iframe은 현재 웹 페이지 안에 또 다른 하나의 웹 페이지를 삽입하는 HTML태그. src 요소를 설정함으로써 삽입할 웹 페이지의 주소가 결정됨.
  2. 10번째 줄은 객체가 성공적으로 로드되었을 때 동작하는 이벤트 핸들러다.
  3. 14~15번째 줄은 로드가 완료되면 iframe내에 삽입된 주소에서 secret-element 객체의 값인 treasure를 읽어와 콘솔에 출력한다.

실습

Same Origin 클릭

Cross Origin 클릭

Cross Origin Resource Sharing(CORS)


SOP 제한 완화

SOP이 클라이언트 사이드 웹 보안에서 중요한 역할을 하지만, 브라우저가 SOP에 구애 받지 않고 외부 출처를 막지 못하는 경우가 있다. 예를 들어, 이미지, 자바스크립트 ,CSS등의 리소스를 불러오는 img, style ,script등의 태그는 SOP의 영향을 받지 않는다.
그리고 웹 서비스에서 동일 출처 정책인 SOP를 완화하여 다른 출처의 데이터를 처리 해야 하는 경우도 있다. 예를 들어,

  • https://cafe.example.com
  • https://blog.example.com
  • https://mail.example.com
    이와 같이 각 서비스의 host의 경우가 다르기 때문에 브라우저는 오리진이 다르다고 인식 할 수 있다.
    이러한 문제를 해결하기 위해 SOP을 적용받지 않고 리소스를 공유 할 수 있는 방법을 교차 출처 리소스 공유 (Cross Origin Resource Sharing, CORS)라고 한다.
    교차 출처의 자원을 공유하는 방법은 CORS와 관련된 HTTP 헤더를 추가하여 전송하는 방법을 사용한다. 이 외에도 JSON with Padding(JSONP)방법을 통해 CORS를 대체할 수 있다.

CORS

교차 출처 리소스 공유 (Cross Origin Resource Sharing, CORS)는 HTTP헤더에 기반하여 Cross Origin 간에 리소스를 공유하는 방법이다. 수신측의 정해진 규칙에 따라 발신측에서 CORS 헤더를 설정해 요청하면, 데이터를 가져갈 수 있도록 설정한다.

웹 리소스 요청 코드

/*
    XMLHttpRequest 객체를 생성합니다. 
    XMLHttpRequest는 웹 브라우저와 웹 서버 간에 데이터 전송을
    도와주는 객체 입니다. 이를 통해 HTTP 요청을 보낼 수 있습니다.
*/
xhr = new XMLHttpRequest();
/* https://theori.io/whoami 페이지에 POST 요청을 보내도록 합니다. */
xhr.open('POST', 'https://theori.io/whoami');
/* HTTP 요청을 보낼 때, 쿠키 정보도 함께 사용하도록 해줍니다. */
xhr.withCredentials = true;
/* HTTP Body를 JSON 형태로 보낼 것이라고 수신측에 알려줍니다. */
xhr.setRequestHeader('Content-Type', 'application/json');
/* xhr 객체를 통해 HTTP 요청을 실행합니다. */
xhr.send("{'data':'WhoAmI'}");

발신측의 HTTP 요청

OPTIONS /whoami HTTP/1.1
Host: theori.io
Connection: keep-alive
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: https://dreamhack.io
Accept: */*
Referer: https://dreamhack.io/

발신측에서 POST 방식으로 HTTP 요청을 보냈지만, OPTIONS 메소드를 가진 HTTP가 전달이 된 후, 수신 측에 웹 리소스를 요청해도 되는 질의하는 과정을 거친다. 이를 CORS preflight이라고 한다.
발신측의 HTTP 요청에서 "Access-Control-Request"로 시작하는 헤더를 살펴보면 헤더 뒤에 따라오는 Method와 Headers는 각각 메소드와 헤더를 추가적으로 사용할 수 있는지 질의한다.

위처럼 질의하면 서버는 다음과 같이 응답한다.

서버의 응답

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://dreamhack.io
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type
Header설명
Access-Control-Allow-Origin헤더 값에 해당하는 Origin에서 들어오는 요청만 처리
Access-Control-Allow-Methods헤더 값에 해당하는 메소드의 요청만 처리
Access-Control-Allow-Credentials쿠키 사용 여부 판단. 예시의 경우 쿠키의 사용을 허용
Access-Control-Allow-Headers헤더 값에 해당하는 헤더의 사용 가능 여부를 나타냄

위 과정을 마친 후, 브라우저는 수신측의 응답과 발신측의 요청이 상응하는지 확인하고, 서버에 POST 요청을 보내 수신측의 웹 리소스를 요청하는 HTTP 요청을 보낸다.

JSON with Padding(JSONP)


위에서는, 이미지나 자바스크립트, CSS 등의 리소스는 SOP에 구애 받지 않고 외부 출처에 대해 접근을 허용한다고 했다. JSONP 방식은 이러한 특징을 이용해 script 태그로 Cross Origin의 데이터를 불러온다. 하지만 script 태그 내에서는 데이터를 자바스크립트의 코드로 인식하기 때문에 Callback 함수를 활용해야 한다. Cross Origin에 요청할 때 callback 파라미터에 어떤 함수로 받아오는 데이터를 핸들링할지 넘겨주면, 대상 서버는 전달된 Callback으로 데이터를 감싸 응답한다.

예시

  • 웹 리소스 요청 코드
<script>
/* myCallback이라는 콜백 함수를 지정합니다. */
function myCallback(data){
    /* 전달받은 인자에서 id를 콘솔에 출력합니다.*/
	console.log(data.id)
}
</script>
<!--
https://theori.io의 스크립트를 로드하는 HTML 코드입니다., callback이라는 이름의 파라미터를 myCallback으로 지정함으로써
수신측에게 myCallback 함수를 사용해 수신받겠다고 알립니다.
-->
<script src='http://theori.io/whoami?callback=myCallback'></script>

마지막 줄에서 Cross Origin의 데이터를 불러온다. 이 때 callback 파라미터로 myCallback을 함께 전달한다. Cross Origin 에서는 응답할 데이터를 myCallback 함수의 인자로 전달될 수 있도록 myCallback으로 감싸 Javascript 코드를 반환해준다. 반환된 코드는 요청측에서 실행되기 때문에 myCallback 함수가 전달된 데이터를 읽을 수 있다.
다만, JSONP는 CORS가 생기기 전에 사용하던 방법으로 현재는 거의 사용하지 않는 추세이다.

  • 웹 리소스 요청에 따른 응답코드
/*
수신측은 myCallback 이라는 함수를 통해 요청측에 데이터를 전달합니다.
전달할 데이터는 현재 theori.io에서 클라이언트가 사용 중인 계정 정보인
{'id': 'dreamhack'} 입니다. 
*/
myCallback({'id':'dreamhack'});

예시2

  • 서버가 반환한 JSON 데이터
{
  "name": "John",
  "age": 30,
  "city": "New York"
}
  • JSONP로 사용하기 위해 콜백 함수로 래핑한 후의 서버의 응답
myCallback({
  "name": "John",
  "age": 30,
  "city": "New York"
});

서버에서 반환된 JSONP응답은 스크립트 태그를 사용하여 웹 페이지에 동적으로 로드된다. 로드된 JSONP 응답은 웹페이지에서 "myCallback"이라는 콜백 함수를 호출하며, 그 결과로 JSON데이터를 사용할 수 있게 된다.

정리


키워드설명
Same Origin Policy(SOP)동일 출처 정책, 현재 페이지의 출처가 아닌 다른 출처로부터 온 데이터를 읽지 못하게 하는 브라우저의 보안 메커니즘
Same Origin현재 페이지와 동일한 출처
Cross Origin현재 페이지와 다른 출처
Cross Origin Resource Sharing(CORS)교차 출처 리소스 공유, SOP의 제한을 받지 않고 Cross Origin의 데이터를 처리 할 수 있도록 해주는 메커니즘

0개의 댓글