쿠키에는 인증 상태를 나타내는 민감한 정보가 보관되며, 브라우저 내부에 저장되어 있다. 브라우저가 웹 서비스에 접속할 때 브라우저는 자동으로 쿠키를 헤더에 포함시켜 request를 서버에 보낸다는 사실을 알고 있다.
만약, 이용자가 악의적인 페이지에 접속할 때, 이 페이지가 JS를 사용해서 이용자의 SNS 웹 서비스로 request를 보내면 어떻게 될까?
브라우저가 request를 보낼 때, 헤더에 해당 웹 서비스 쿠키를 포함시킬 것이다. 따라서 JS로 request를 보낸 페이지는 로그인된 이용자의 SNS response를 받을 것이다. 또한, 마음대로 페이지 접속자의 SNS에 글을 올리거나, 삭제하고, SNS 메신저를 읽는 것도 가능할 것이다.
이러한 문제점을 막기 위해 동일 출처 정책, Same Origin Policy(SOP) 보안 메커니즘이 탄생했다. 이는 클라이언트 사이드 웹 보안에 있어서 중요한 요소이다.
Same Origin Policy(SOP)
: 브라우저는 해당 웹 서비스에스 사용하는 인증 정보인 쿠키를 HTTP request에 포함시켜서 전달한다. 웹 리소스를 통해 타 사이트에 접근할 때도 이를 함께 전송하는 특징을 가지고 있다. 이러한 특징 때문에 악의적인 페이지가 클라이언트의 권한을 이용해 대상 사이트에 HTTP request를 보내고 response를 받는 코드를 실행할 수 있다.
이러한 문제점을 막기 위해 클라이언트 입장에서 가져온 데이터를 악의적인 페이지에서 읽지 못하도록 하는 것이다.
SOP의 오리진(Origin) 구분 방법
오리진은 프로토콜(Protocol, Scheme), 포트, 호스트로 구성된다. 구성 요소가 모두 일치해야 동일한 오리진이다.
예를 들어, https://same-origin.com/ 오리진과 아래 URL을 비교했을 때 결과는 다음과 같다.
- https://same-origin.com/frame.html
- http://same-origin.com/frame.html
- https://cross.same-origin.com/frame.html
- https://same-origin.com:1234/
1번은 Same Origin이고, Path만 다르다.
2번은 Cross Origin이고, Scheme가 다르다.
3번은 Cross Origin이고, Host가 다르다.
4번은 Cross Origin이고, Port가 다르다.
따라서, 1번만 Same Origin이므로, SOP는 1번일 때만 정보를 읽을 수 있도록 해준다.
앞서 언급한 것처럼, SOP는 클라이언트 사이드 웹 보안에서 중요한 요소이다. 하지만, 브라우저가 이러한 SOP에 구애받지 않고 외부 출처에 대한 접근을 허용해 주는 경우가 존재한다. 예를 들면 <img>, <style>, <script> 등의 태그는 SOP의 영향을 받지 않는다.
위 경우들 이외에도 웹 서비스에서 동일 출처 정책인 SOP를 완화하여 다른 출처의 데이터를 처리해야 하는 경우도 있다.
예를 들어, 특정 포털 사이트가 카페, 블로그, 메일 서비스를 아래의 주소로 운영하고 있다고 하자. 각 서비스의 Host가 다르기 때문에 브라우저는 각 사이트의 오리진이 다르다고 인식한다.
- 카페 : https://cafe.whitetommy.io
- 블로그: https://blog.whitetommy.io
- 메일: https://mail.whitetommy.io
이러한 환경에서, 이용자가 수신한 메일의 개수를 메인 페이지에 출력하려면, 개발자는 메인 페이지에서 메일 서비스에 관련된 리소스를 요청하도록 해야 한다. 이때, 두 사이트는 오리진이 다르므로 SOP를 적용받지 않고 리소스를 공유할 방법이 필요하다.
Cross Origin Resource Sharing(CORS)
: SOP를 적용받지 않고 다른 오리진이 서로 리소스를 공유하기 위해 사용하는 교차 출처 리소스 공유 방식이다.
- 리소스 공유 방법 : CORS와 관련된 HTTP 헤더를 추가하여 전송
발신 측에서 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 request를 보냈으나, OPTIONS 메서드를 가진 HTTP request가 전달된 것을 확인할 수 있다. 이를 CORS preflight라고 하며, 수신측에 웹 리소스를 요청해도 되는지 질의하는 과정이다.
또한, Acess-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
- Access-Control-Allow-Origin : 헤더 값에 해당하는 Origin에서 들어오는 요청만 처리한다.
- Access-Control-Allow-Methods : 헤더 값에 해당하는 메서드의 요청만 처리한다.
- Access-Control-Allow-Credentials : 쿠키 사용 여부를 판단한다. true면 허용한다.
- Access-Control-Allow-Headers: 헤더 값에 해당하는 헤더의 사용 가능 여부를 나타낸다.
위의 과정을 마치면, 브라우저는 수신측의 응답이 발신 측의 요청과 상응하는지 확인하고 나서 POST 요청을 보내 수신 측의 웹 리소스의 요청하는 HTTP request를 보낸다.
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의 data를 불러와서 응답할 데이터를 myCallback 함수의 인자로 전달될 수 있도록 myCallback으로 감싸 Javascript 코드를 반환해준다. 반환된 코드는 요청 측에서 실행되기 때문에 myCallback 함수가 전달된 데이터를 읽을 수 있다.
아래는 웹 리소스 요청에 따른 응답 코드이다.
/*
수신측은 myCallback 이라는 함수를 통해 요청측에 데이터를 전달합니다.
전달할 데이터는 현재 theori.io에서 클라이언트가 사용 중인 계정 정보인
{'id': 'dreamhack'} 입니다.
*/
myCallback({'id':'dreamhack'});
이 방법은 현재 잘 사용하지 않기 때문에, 새롭게 코드를 작성할 때에는 CORS를 사용한다.
'해킹 > 웹 해킹(Web Hacking)' 카테고리의 다른 글
XSS란? (0) | 2024.01.06 |
---|---|
쿠키와 세션(Cookie & Session) (0) | 2023.12.28 |
개발자 도구와 기능 (0) | 2023.12.27 |
웹 브라우저(Web Browser) (0) | 2023.12.26 |
HTTP란? (0) | 2023.12.26 |