펜테스트짐


훈련을 통해 당신의 실력을 향상시켜보세요.



웹 클라이언트측 기술

모두 55명의 회원님이 완료했어요.


이 훈련에서는 웹 애플리케이션이 서비스를 하기 위해 클라이언트측에서 사용될 수 있는 HTML, CSS, 자바스크립트, 문서객체모델(DOM) 등의 다양한 기술들에 대해 알아봅니다.




클라이언트측(Client-Side) 기술이란 클라이언트-서버 모델에서 클라이언트, 일반적으로는 사용자의 웹 브라우저에서 실행되는 기술을 말합니다. 클라이언트측에서는 서버에서 응답받은 HTML, CSS를 파싱하여 웹 브라우저 화면에 그리고, 자바스크립트를 해석하여 사용자단의 동적 기능을 수행합니다.


클라이언트측의 위치


클라이언트측에서 사용되는 기술은 다양하게 존재하지만 이번 훈련에서는 아래의 기술에 대해서만 다루겠습니다.

  • HTML
  • CSS
  • 자바스크립트
  • DOM
  • Ajax
  • JSON
  • SOP
  • CORS


HTML


HTML(Hyper Text Markup Language)이란 웹 애플리케이션, 정확히는 웹 페이지를 제작하기 위해 사용되는 마크업 언어입니다. HTML은 아래와 같이 꺽쇠괄호(<와 >)에 감싸여 있는 시작 태그와 종료 태그, 그리고 /를 사용하며 제목, 링크, 내용 등을 표시하는 등 구조적으로 웹 페이지를 만들 수 있도록 지원합니다.

<tag-name>contents</tag-name>

또는 다음과 같이 종료 태그가 없이 사용되기도 합니다.

<tag-name attribute="some-value">

또는 위 두 가지의 형식이 복합적으로 사용되는 경우도 있습니다.

<tag-name attribute="some-value"></tag-name>

HTML 태그의 종류에 따라 위 형식 중 어떤 것을 따를지 결정되며, 위의 형식을 HTML 요소(Element)라고 부릅니다.

HTML은 프로그래밍 언어가 아니므로 HTML만으로는 동적 기능이 포함된 웹 페이지를 제작할 수 없으며,  HTML 파일은 .html 이나 .htm 확장자를 갖습니다.

아래는 HTML 파일의 간단한 예제입니다.

<html>
  <head><title>HTML 파일 예제</title></head>
  <body>
    <div>
      <h1>이 예제는 매우 간단한 HTML 파일입니다.</h1>
      <img src="이미지 파일의 주소나 경로">
      <a href="링크 주소">하이퍼링크</a>
    </div>
    <form action="/login" method="POST">
      아이디: <input type="text" name="username" id="username">
      패스워드: <input type="password" name="password" id="password">
      <input type="submit" name="login" value="로그인">
    </form>
  </body>
</html>


하이퍼 링크

HTML 문서안에서 문서, 이미지, 동영상 등의 여러 형식의 리소스를 서로 연결할 수 있는 기술로 클라이언트가 서버에 페이지 이동 등의 요청을 보낼 때 사용됩니다. 다음의 앵커 태그 <a>가 대표적인 예입니다. 앵커 태그를 통해 발생한 요청은 GET 메소드를 통해 전송됩니다. 

<a href="/other-page">하이퍼링크</a>


사용자로부터 입력을 받아 데이터를 수집하거나 가공하여 처리하려면 폼을 사용해야 합니다. 입력된 데이터는 웹 애플리케이션에 구현된 로직에 따라 서버측 프로그래밍 언어를 통해 처리됩니다. 폼에 사용할 수 있는 요청 메소드는 GET, POST 모두 가능하지만 일반적으로는 POST 메소드를 통해 전송합니다. 흔히 볼 수 있는 회원가입, 로그인 등은 대부분 폼으로 구현되어 있습니다. 

다음은 일반적으로 회원가입을 처리하는 폼의 예제입니다. 아래 예제에서는 사용자가 "사용자명", "이메일 주소", "나이"라는 입력 필드에 값을 채워 "가입" 버튼을 클릭하면 POST 요청으로 사용자가 입력한 매개변수와 값은 메세지 바디 영역에 포함되어 서버로 전송됩니다.

<form action="/join" method="POST">
사용자명: <input type="text" name="username">
이메일 주소: <input type="email" name="email">
나이: <input type="text" name="age">
<input type="submit" name="submit" value="가입">
</form>


CSS


CSS(Cascading Style Sheet)는 각 HTML 요소의 모양, 색상, 크기 등을 디자인하는 등 웹 페이지가 사용자에게 보여지는 방법을 지정하는 언어입니다. CSS를 사용함으로써 디자인적 측면에서는 보다 멋스러운 웹 페이지를 제작할 수 있으며, 개발이나 관리적인 측면에서는 HTML 컨텐츠와 스타일적인 부분을 분리할 수 있으며, HTML 페이지를 심플하게 만들수 있고, 추후 구조 변경 등이 용이하게 됩니다.  

CSS를 적용하는 방법에는 세 가지가 있습니다.

  • 내부(Internal) CSS 
  • 외부(External) CSS
  • 인라인(In-Line) CSS


내부 CSS와 외부 CSS에서 사용되는 기본 구문은 다음과 같습니다.

selector {
   property: value;
}
  • selector: 스타일을 지정할 HTML 요소를 선택하는 선택자입니다. HTML 태그명(h1, a, p 등)이나 요소에 지정된 아이디 등이 사용될 수 있습니다. 선택자에 대해 더 알아보시려면 MDN의 Selector(CSS)에 관한 문서를 읽어보세요.
  • property: selector에 의해 선택된 HTML 요소를 웹 브라우저가 표시할 방법을 정의하는 속성입니다. color나 font-size 등이 있습니다.
  • value: 속성이 가질 값을 지정합니다.


다음은 p 태그의 색상은 빨간색, 글자 크기는 20px로 스타일을 지정하기 위한 CSS 기본 구문의 예시입니다.

p {
  color: red;
  font-size: 20px;
}


그럼 CSS를 적용하는 세 가지 방법에 대해 예시를 살펴보겠습니다.


내부 CSS

먼저 내부 CSS는 HTML 파일 내부에서 CSS 기본 구문을 <style></style> 태그로 감싼 형태로 정의됩니다. 아래의 예시는 p 태그명에 스타일을 지정하는 예시입니다. 

<html>
<head>
  <title>내부 CSS (HTML 태그명)</title>
  <style>
      p {
          color: red;
          font-size: 20px;
      }
  </style>
</head>
<body>
  <p>버그바운티 클럽</p>
</body>
</html>


선택자로 HTML 태그명 뿐만 아니라 클래스명, 아이디를 사용할 수도 있다고 말씀드렸습니다. 

클래스명을 사용할 때는 다음과 같이 클래스명을 선택자(.myclass)로 지정하여 CSS를 정의합니다. 클래스명을 선택자로 사용할 때는 클래스명 앞에 Dot(.)을 사용해줘야 합니다. 그리고 해당 스타일을 적용할 p 태그에 class ="myClass"를 추가하기만 하면 됩니다. HTML 페이지에서 class 속성이 myClass로 지정된 요소는 모두 영향을 받습니다.

<html>
<head>
  <title>내부 CSS (클래스)</title>
  <style>
      .myClass {
          color: red;
          font-size: 20px;
      }
  </style>
</head>
<body>
  <p class="myClass">버그바운티 클럽</p>
</body>
</html>


HTML 요소의 아이디를 통해 스타일을 지정하는 방법은 아래와 같습니다. 아이디를 선택자로 사용할 때는 아이디 앞에 샾(#)을 사용해야 합니다. 

<html>
<head>
  <title>내부 CSS (요소 아이디)</title>
  <style>
      #myID {
          color: red;
          font-size: 20px;
      }
  </style>
</head>
<body>
  <p id="myID">버그바운티 클럽</p>
</body>
</html>


외부 CSS

외부 CSS는 HTML 파일과 별개의 파일에서 기본 구문에 따라 정의됩니다.

<html>
<head>
  <title>외부 CSS</title>
  <link rel="stylesheet" type="text/css" href="/파일경로/myStyle.css">
</head>
<body>
  <p>버그바운티 클럽</p>
</body>
</html>
/* 외부 CSS 파일 (myStyle.css) */p {  color: red;  font-size: 20px;}

인라인 CSS

인라인 CSS는 HTML 요소 내부에서 직접 지정하는 방법으로 다음과 같이 조금 다른 구문을 사용하게 됩니다.

<tag-name style="property1: value1; property2: value2;">

역시 p 태그의 색상은 빨간색, 글자 크기는 20px로 스타일을 지정하기 위한 인라인 CSS의 예시입니다.

<p style="color: red; font-size: 20px;">버그바운티 클럽</p>

인라인 CSS는 지정된 HTML 요소에만 유효하며, 다른 요소에는 영향이 없습니다. 우선순위는 더 높습니다.


자바스크립트


앞서 설명드린 HTML과 CSS만을 사용해 제작된 웹 페이지는 정적인 컨텐츠만 제공할 수 있습니다. 이런 웹 페이지의 인터페이스는 사용자에게 심심함과 불편을 느끼게 합니다. 자바스크립트(Javascript)는 HTML, CSS와 통합되어 웹 페이지에 클라이언트측에서 동작하는 동적 기능을 구현하기 위해 사용되는 스크립트 언어입니다. 동적 기능으로는 웹 페이지에 보이는 이미지가 움직이거나 슬라이드쇼를 자동으로 순환되게 하거나 또는 검색어를 타이핑할 때마다 실시간으로 검색어를 자동 추천해주는 기능 등 우리가 웹 애플리케이션을 이용하면서 흔히 접하는 많은 기능들이 자바스크립트를 통해 구현되어 있습니다. 자바스크립트는 전통적으로 웹 브라우저에서 동작하는 클라이언트측 언어였으나 Node.js의 등장으로 인해 서버측 개발에서도 사용되는 사실상 범용 개발 언어가 되었습니다. 그래도 여전히 클라이언트측 언어로써 가장 많이 이용되고 있습니다. 


자바스크립트는 일반적으로 다음과 같은 일을 할 수 있습니다.

  • HTML을 추가하거나 변경하고 스타일을 수정합니다.
  • 사용자의 특정 동작(마우스 클릭, 마우스 포인터 이동, 키보드 누름 등)에 따른 기능을 수행합니다.
  • Ajax와 같은 기술을 사용해 원격 서버로 요청을 보내고 파일을 업로드, 다운로드합니다.
  • 쿠키를 설정 혹은 조회하거나 사용자에게 메시지를 표시합니다.
  • 클라이언트측(로컬 스토리지)에 데이터를 저장합니다.


자바스크립트를 HTML 페이지에 로드하는 방법은 CSS와 유사합니다.

  • 내부 자바스크립트
  • 외부 자바스크립트
  • 인라인(In-Line)


내부 자바스크립트

내부 자바스크립트는 HTML 파일 내부에서 <script></script> 태그를 안에 자바스크립트를 구현하는 방법입니다. HTML 파일 내부에서 구현한 예시는 다음과 같습니다.

<html>
  <head>
    <title>내부 자바스크립트</title>
    <script>
      //여기에 자바스크립트 코드
    </script>
  </head>
  <body>
    ...생략...
  </body>
</html>

외부 자바스크립트

HTML 파일이 아닌 별도의 파일에 자바스크립트를 작성하고 HTML 페이지에서 이 파일을 로드하는 방법이 있습니다. 이 경우 다음과 같이 js라는 확장자명을 가진 파일을 작성하고, HTML 페이지에서는 <script src="/파일경로/myScript.js"></script>를 사용하여 해당 js파일을 웹 페이지내에 포함시킵니다.

<!-- HTML 파일 -->
<html>
  <head>
    <title>외부 자바스크립트</title>
    <script src="/파일경로/myScript.js"></script>
  </head>
  <body>
    ...생략...
  </body>
</html>
<!-- 별도 자바스크립트 파일 (myScript.js) -->
<script>
//여기에 자바스크립트 코드
</script>


인라인

HTML 태그 요소 안에 직접 자바스크립트를 작성합니다. 이 경우에는 HTML 태그가 사용할 수 있는 적절한 이벤트가 필요합니다.

다음은 버튼을 클릭할 때 'Hello, hunters!'라는 알림창을 띄우게 됩니다.

<button onclick="alert('Hello, hunters!')">Greeting</button>



HTML, 자바스크립트(JS), CSS를 사람에 비유하여 잘 표현했다.(출처: Step IT Academy Cambodia)


DOM (HTML DOM)


DOM(Document Object Model: 문서객체모델)은 웹 페이지의 구조화되고 계층적인 구조의 지도를 만드는 도구입니다. 다시 말해 HTML이나 XML과 같은 문서가 웹 브라우저에 로드되면 문서를 이루는 텍스트(Text)나 각 요소(Element), 속성(Attribute) 등은 노드(Node)라고 불리는 문서 객체가 되며 이 각각의 노드들을 아래와 같은 DOM Tree라는 논리적인 트리 구조를 통해 나타내게 됩니다. 최상위 노드는 "Document" 노드이며 텍스트 노드, 요소 노드, 속성 노드들은 이 Document  노드의 자식 노드가 됩니다.



DOM Tree (출처: 위키백과)


DOM은 플랫폼 및 언어 독립적인 프로그래밍 인터페이스를 제공하는데 일반적으로 자바스크립트 객체를 통해 제공합니다. 이를 DOM API라고 합니다. DPM API를 통해 개발자는 웹 페이지의 구성 요소에 보다 쉽게 접근하여 구조를 탐색하고, 문서의 내용이나 속성, 스타일을 추가, 삭제, 변경할 수 있도록 해줍니다. 만일 DOM이 없다면 자바스크립트와 같은 프로그래밍 언어를 통해 문서의 구조나 스타일, 내용을 제어할 수 없습니다.  

DOM API는 각 객체의 메소드와 속성을 통해 DOM에 접근할 수 있게 합니다. 메소드를 통해 HTML 요소에 접근하거나 추가 또는 삭제할 수 있으며 속성을 통해 HTML 요소의 값을 변경하거나 읽을 수 있습니다.

다음은 아이디가 greeting으로 지정된 <p> 요소의 값을 "Hello, hunters!"로 변경하는 예시입니다. 이 예시에서 메소드는 getElementById이며, 속성은 innerHTML입니다.

<html>
...생략...
<body>
<p id="greeting"></p>
<script>
document.getElementById("greeting").innerHTML = "Hello, hunters!";
</script>
</body>
</html>
  • document: DOM Tree에서 최상위 노드입니다.
  • getElementById("greeting"): 아이디가 greeting인 HTML 요소에 접근합니다.
  • innerHTML: 지정된 HTML 요소의 값을 나타냅니다. 

 

DOM API에 대해 더 많은 정보를 읽어보시려면 MDN의 DOM Interface에 관한 문서를 확인해보시기 바랍니다.


Ajax


Ajax에 대한 위키백과의 정의는 다음과 같습니다.

"Ajax(Asynchronous Javascript and XML)는 비동기적인 웹 애플리케이션의 제작을 위해 아래와 같은 조합을 이용하는 웹 애플리케이션 개발 기법이다.
  • 표현 정보를 위한 HTML (또는 XHTML) 과 CSS
  • 동적인 화면 출력 및 표시 정보와의 상호작용을 위한 DOM, 자바스크립트
  • 웹 서버와 비동기적으로 데이터를 교환하고 조작하기 위한 XML, XSLT, XMLHttpRequest (Ajax 애플리케이션은 XML/XSLT 대신 미리 정의된 HTML이나 일반 텍스트, JSON, JSON-RPC를 이용할 수 있다). (이하 생략..)"

이미 눈치채신 분들도 계시겠지만 Ajax는 특정 기술이 아닙니다. 위키백과의 정의와 같이 HTML, CSS, 자바스크립트, DOM, XMLHttpRequest 등의 여러 기술을 조합해 만들어 낸 새로운 개발 기법으로, Asynchronous Javascript and XML이라는 이름을 보면 아시다시피 자바스크립트와 XML을 이용해 비동기적으로 웹 서버와 데이터를 교환합니다. 초창기에는 데이터를 교환하기 위해 XML을 사용하였기 때문에 이름에 XML이 사용되었지만 현재는 다양한 포맷을 지원하고 있으며 JSON을 사용하는 것이 일반적입니다.

전통적인 웹 애플리케이션은 HTTP 요청과 응답을 통해 웹 페이지 단위로 갱신이 되었었습니다. 이러한 방식은 페이지의 일부분만 갱신이 필요한 기능이라도 어김없이 다시 웹 페이지의 이미지나, 스크립트 등의 전체 리소스를 주고 받게 되므로 서버나 클라이언트 모두에게 자원과 시간을 낭비하게 하는 셈입니다. 하지만 Ajax를 사용하면 전체 페이지를 다시 갱신할 필요 없이 XMLHttpRequest 객체를 통해 필요한 부분만 비동기적으로 갱신할 수 있습니다. Ajax의 등장으로 SPA(Sigle Page Application, 단일 페이지 애플리케이션)의 시대를 맞이하게 됩니다. SPA는 웹 사이트의 기본 골격을 이루는 페이지는 하나만 구현해두고 Ajax를 이용해 데이터만 받아 적절한 위치에 보여주는 방식입니다. 사실 Ajax가 등장하기 이전 Microsoft의 Internet Explorer 5에는 윈도우 ActiveX 기반의 Microsoft.XMLHTTP라는 컴포넌트가 있었습니다. 오늘날의 Ajax와 동일한 기능을 제공했지만 개발자들 사이에서 그리 주목받지 못하다가 모질라 파이어폭스와 사파리, 오페라 웹 브라우저 등에서 순차적으로 Ajax 기술을 정식 도입하고 구글 지도에 채택되면서부터 주목을 받기 시작하고, 현재의 Ajax라는 이름으로 불려지게 되었습니다.  


Ajax는 다음의 과정을 통해 수행됩니다.

  1. 클라이언트의 이벤트 발생
  2. XMLHttpRequest 객체 생성
  3. XMLHttpRequest 객체 구성
  4. XMLHttpRequest를 통한 비동기 요청
  5. (서버가 응답 시) XMLHttpRequest 객체의 콜백함수 호출
  6. HTML DOM 갱신


다음 예제를 통해 각 과정에 대해 좀 더 살펴보겠습니다.

<html>
   <head>
      <title>XMLHttpRequest 예제</title>
      <script>
         function test() {
            var xhr = new XMLHttpRequest();                        // 2. XMLHttpRequest 객체 생성
            xhr.onreadystatechange = function() {                  // 5. XMLHttpRequest 객체의 콜백함수 호출
                if (xhr.readyState == 4 && xhr.status == 200) {
                        document.getElementById("result").innerHTML = xhr.responseText; // 6. HTML DOM 갱신
                }            };
            xhr.open("GET", "https://www.bugbountyclub.com",true); // 3. XMLHttpRequest 객체 구성
            xhr.send();                                            // 4. XMLHttpRequest를 통한 비동기 요청
         }
      </script>
   </head>
   <body>
      <button type="button" onclick="test()">Send</button>         // 1. 클라이언트의 이벤트 발생
      <div id="result"></div>
   </body>
</html>


1. 클라이언트 이벤트 발생

<button type="button" onclick="test()">Send</button>

사용자의 버튼 클릭이라는 이벤트가 발생했을 때 자바스크립트의 test() 메소드를 실행합니다.


2. XMLHttpRequest 객체 생성

var xhr = new XMLHttpRequest();

자바스크립트의 XMLHttpRequest 라이브러리를 통해 XMLHttpRequest 객체를 생성합니다.



3. XMLHttpRequest 객체 구성

xhr.open("GET", "https://www.bugbountyclub.com",true);

XMLHttpRequest 객체의 open() 메소드를 통해 요청 메소드(GET, POST 등), 요청을 보낼 URL, 요청의 비동기 여부를 true(기본값) 또는 false의 값으로 설정합니다.


4. XMLHttpRequest를 통한 비동기 요청

xhr.send();

XMLHttpRequest 객체를 통해 서버로 비동기 요청을 전송한다.


5. XMLHttpRequest 객체의 콜백함수 호출

xhr.onreadystatechange = function() {    if (xhr.readyState == 4 && xhr.status == 200) {                 // 6번의 HTML DOM 갱신을 위한 코드가 여기에 들어갑니다.    }};

♦ onreadystatechange: XMlHttpRequest 객체의 상태가 변경되었을 때의 이벤트를 감지합니다. 서버에서 응답을 받게 되면 상태가 변경되고, function이 실행합니다.

♦ readyState: XMlHttpRequest 객체의 현재 상태를 나타내며, 값에 따른 정의는 다음과 같습니다. 

  • 0: 요청 초기화되지 않았습니다. open() 메소드를 호출하기 전의 상태입니다.
  • 1: 요청이 설정되었습니다. open() 메소드를 호출하고 send()를 호출하기 전의 상태입니다.
  • 2: 요청이 전송되었습니다. send() 메소드를 호출한 후의 상태입니다.
  • 3: 요청이 처리 중입니다.  서버에 요청을 전송한 후 서버에서 응답을 받기 전의 상태입니다.
  • 4: 요청이 완료되었습니다. 서버로 부터 응답을 받은 후의 상태입니다.

♦ status: HTTP 응답 코드를 나타냅니다.


6. HTML DOM 갱신

document.getElementById("result").innerHTML = xhr.responseText;

DOM API를 사용해 서버의 응답을 "result"라는 아이디를 갖는 HTML 요소에 표시합니다.

♦ responseText: 서버의 응답을 텍스트로 반환합니다.


JSON


JSON(JavaScript Object Notation)은 데이터를 저장하고 전송하기 위한 초경량의 데이터 형식입니다. 과거부터 사용되던 XML보다 높은 가독성과 가벼운 용량이 장점이며 결국 XML을 대신하여 Ajax 기반의 애플리케이션에서 서버와 애플리케이션간에 데이터를 전송하는데 주로 사용됩니다.  

한 웹 애플리케이션에서 사용자의 애완동물 리스트를 조회하는 기능이 있다고 가정해봅시다.

만일 사용자가 어떤 사용자의 애완동물 리스트 조회를 클릭한다면 서버는 다음과 같은 JSON의 형식의 데이터를 반환하고 웹 브라우저는 JSON 데이터를 파싱하고 렌더링하여 사용자에게 표시하게 됩니다.

{
    "pet": [
        {
          "name": "Tommy",
            "type": "dog",
            "age": 10
            "like": ["ball", "running"]
        }
        {
            "name": "Sally",
            "type": "cat",
            "age": 4
            "like": ["fish", ""]
        }
    ]
}
  • 중괄호 { }는 객체를 나타내며 객체는 콤마(,)로 구분된 이름:값 쌍의 집합입니다.  
  • 대괄호 [  ]는 배열을 나타냅니다. 배열의 요소들은 콤마(,)로 구분됩니다.


SOP


SOP(Same-Origin Policy, 동일 출처 정책)는 사용자를 보호하기 위한 웹 브라우저의 기본적인 보안 메커니즘으로써, 자바스크립트 엔진 표준 스펙에 포함되어 있습니다. SOP는 어떤 출처에서 스크립트를 통해 다른 출처의 리소스에 접근하는 것을 제한하는 정책입니다. SOP가 없다면 Gmail을 사용 중이던 한 사용자가 다른 웹 브라우저 창이나 탭에서 악의적인 웹 사이트를 방문한 경우 악의적인 웹 사이트는 Gmail과 출처가 다름에도 불구하고 해당 사용자의 Gmail 데이터를 읽거나 기능을 사용할 수 있게 됩니다. 


출처란 무엇인가?

URL 스키마(프로토콜), 호스트, 포트까지를 출처(Origin)라고 합니다. 즉, URL 스키마, 호스트, 포트가 모두 동일하다면 동일 출처이며, 어느 하나라도 다른 경우에는 다른 출처입니다. 

https://www.bugbountyclub.com/sop/origin.html와 다음의 URL을 비교해보면 출처의 개념을 이해하시기가 보다 쉬울 것입니다.

URL동일 출처 여부사유
https://www.bugbountyclub.com/sop2/origin2.html동일 출처URL 스키마, 호스트, 포트 동일하고 경로만 다름.
http://www.bugbountyclub.com/sop/origin.html다른 출처URL 스키마가 다름.
https://test.bugbountyclub.com/sop/origin.html다른 출처호스트가 다름.
https://www.bugbountyclub.com:8443/sop/origin.html다른 출처포트가 다름.
http://test.bugbountyclub.com/sop/origin2.html다른 출처URL 스키마, 호스트가 다름.
https://test.bugbountyclub.com:8443/sop2/origin2.html다른 출처호스트, 포트가 다름.
http://www.bugbountyclub.com:8080/sop/origin.html다른 출처URL 스키마, 포트가 다름.
http://test.bugbountyclub.com:8080/sop/origin.html다른 출처URL 스키마, 호스트, 포트 모두 다름.


SOP는 어떤 출처에서 다른 출처로의 요청 자체를 차단하는 것으로 오해할 수도 있으나 다른 출처로 요청을 전송할 수는 있으나 수신된 응답을 읽을 수 없게 제한하는 기능입니다. SOP로 인해 한 출처에서 다른 출처의 쿠키를 가져오거나, 다른 출처의 DOM에 접근해 조작하거나 Ajax를 호출하는 등의 작업은 철저히 차단됩니다. 하지만 SOP에 적용받지 않는 것들도 있습니다. 일반적으로 SOP는 다른 출처의 리소스의 삽입은 허용합니다만 스크립트를 통해 다른 출처의 리소스를 읽는 것은 엄격히 차단됩니다.


허용되는 것들

  • 폼 제출: <form action="..."> 태그는 다른 출처로의 폼 제출이 가능합니다.
  • 이미지 삽입: <img src="..."> 태그는 다른 출처의 이미지를 가져와 삽입할 수 있습니다.
  • 멀티미디어 삽입: <audio src="...">, <video src="...">, <embed src="..."> 등의 태그는 다른 출처의 멀티미디어 리소스를 삽입할 수 있습니다.
  • iframe: <iframe src="...">는 다른 출처의 리소스를 가져와 표시할 수 있습니다. 다만 x-frame-options 헤더의 설정에 따라 결과는 다를 수 있습니다.
  • 스크립트 삽입: <script src="...">를 통해 다른 출처의 스크립트를 로드하고 실행할 수 있습니다.
  • CSS 삽입: <link rel="stylesheet" href="...">를 통해 다른 출처의 스크립트를 로드하고 실행할 수 있습니다.


차단되는 것들

스크립트내에서 다른 출처의 리소스를 접근하거나 읽는 것은 SOP에 의해 차단됩니다.

  • <iframe src="...">을 통해 삽입된 다른 출처의 리소스에 자바스크립트를 통해 접근하여 읽는 것은 금지됩니다.
const myframe = document.getElementById(iframe");
const message = myframe.contentDocument.getElementById("message").innerText;
  • 자바스크립트를 통해 <canvas> 태그에 교차 출처의 이미지를 로드하는 것은 금지됩니다.
<canvas id="myCanvas"></canvas>
var context = document.getElementById('myCanvas').getContext('2d');
var img = new Image();
img.onload = function() {
  context.drawImage(img, 0, 0);
};
img.src = 'https://cross-origin.com/image.svg';


하지만, 오늘 날의 대규모 웹 애플리케이션은 운영과 관리의 효율성을 위해 목적과 용도에 따라 서버를 분리하고 하위 도메인 등의 다른 출처의 리소스를 가져다 쓰는 일이 빈번하게 발생합니다. 즉 다른 출처의 리소스를 불가피하게 사용할 수 밖에 없는데 이 때 SOP의 제한을 완화하기 위한 기술로 CORS와 JSONP 등이 있습니다.


CORS


CORS(Cross-Origin Resource Sharing, 교차 출처 자원 공유)는 말씀드린 바와 같이  SOP의 엄격한 제한을 우회하고자 만들어진 기술입니다. CORS는 어떤 출처의 리소스를 다른 출처에서 접근할 수 있도록 허용하는 방법이며, Access-Control-Allow-Origin과 같은 특수한 HTTP 응답 헤더를 통해 권한을 부여합니다. CORS의 구현을 위해 사용되는 특수한 헤더들은 아래와 같습니다.


요청 헤더

  • Origin: 요청을 전송한 출처를 서버에게 알립니다.
  • Access-Control-Request-Method: 실제 요청시 클라이언트가 사용할 HTTP 요청 메소드(GET, POST, PUT 등)를 서버에게 알립니다.
  • Access-Control-Request-Headers: 실제 요청시 클라이언트가 보내게 될 HTTP 헤더를 서버에게 알립니다.


응답 헤더

  • Access-Control-Allow-Origin: 리소스가 공유될 수 있는 출처를 클라이언트에게 알립니다.
  • Access-Control-Allow-Credentials: 자격증명이 있는 요청에 대해 true로 설정 시 자바스크립트에 응답을 노출하도록 합니다. 
  • Access-Control-Allow-Methods: 리소스에 접근할 때 허용되는 HTTP 요청 메소드를 클라이언트에게 알립니다.
  • Access-Control-Allow-Headers: 실제 요청에 허용되는 HTTP 헤더를 클라이언트에게 알립니다.
  • Access-Control-Expose-Headers: 응답으로 노출될 수 있는 헤더를 클라이언트에게 알립니다.
  • Access-Control-Allow-Max-Age: Access-Control-Allow-Methods, Access-Control-Allow-Headers 헤더를 캐시할 수 있는 기간을 클라이언트에게 알립니다.


CORS 요청에는 두 가지 방식이 있습니다.

  • Simple Request
  • Preflighted Request


Simple Request

먼저 사용자가 https://foo.example이라는 웹 사이트에 방문한 상태에서 https://bar.other이라는 다른 출처의 리소스를 가져온다고 가정해봅시다. 그림과 같이 요청에는 Origin 헤더를 추가하여 요청이 발행된 출처인 foo.example을 설정하여 서버에게 전송합니다. 서버는 Origin 요청 헤더를 통해 요청이 어떤 출처로 부터 왔는지 알게 됩니다. 또한 서버는 와일드카드(*)의 값을 갖는 Access-Control-Allow-Origin(ACAO) 헤더를 추가하여 클라이언트에게 응답하고 이로써 해당 리소스는 클라이언트에게 허용되게 됩니다. ACAO 헤더의 값이 와일드카드인 경우는 모든 출처에 허용한다는 의미입니다.



Simple Request (출처: MDN)


특정 출처만 허용하고 싶은 경우에는 HTTP 응답에서 다음과 같이 해당 출처만 ACAO 헤더에 설정하면 됩니다.

Access-Control-Allow-Origin: foo.example


Preflighted Request

Preflighted Request는 Simple Request와 달리 실제 요청을 보내기 전에 OPTIONS 헤더를 통해 클라이언트와 서버간 사전 합의를 거칩니다. 이를 Preflighted 요청이라고 부릅니다. Preflighted 요청에서는 클라이언트가 Origin 헤더와 더불어 Access-Control-Request-Method와 Access-Control-Request-Headers 헤더를 통해 실제 요청에서 사용할 메소드와 헤더를 서버에게 알리고 허용되는지 파악합니다. 서버는 클라이언트에게 요청에서 허용되는 메소드와 헤더 정보를 알리는 Access-Control-Allow-Methods, Access-Control-Allow-Headers 헤더를 추가하여 응답합니다. Preflighted 요청 이후에 실제 요청과 응답이 이루어집니다.

 

Preflighted Request (출처: MDN)


CORS와 관련된 웹 애플리케이션의 취약점으로는 OWASP TOP 10의 #5 잘못된 보안 구성(Security Misconfiguration)에 해당되는 "잘못된 CORS 설정(CORS Misconfiguration)"이 있으며, 버그바운티에서 많이 제보되는 취약점 중 하나입니다. 이 취약점으로 인해 의도하지 않은 다른 출처에 사용자의 자격증명 등의 민감한 정보가 노출될 수 있습니다. 


참고 문헌