Post

CORS

출처

  • 출처(origin)는 접근할 때 사용하는 URL의 스킴(프로토콜, 호스트(도메인), 포트로 정의
  • 두 객체의 스킴, 호스트, 포트가 모두 일치하는 경우 같은 출처를 가졌다고 한다.

동일 출처 예시

  • 스킴과 호스트 일치

    • http://example.com/app1/index.html

      http://example.com/app2/index.html

  • HTTP의 기본 포트는 80

    • http://Example.com:80

      http://example.com

  • 경로만 불일치

    • http://store.company.com/dir/page.html

      http://store.company.com/dir2/other.html

      http://store.company.com/dir/inner/another.html

다른 출처 예시

  • 스킴 불일치

    • http://example.com/app1

      https://example.com/app2

  • 호스트 불일치

    • http://example.com

      http://www.example.com

      http://myapp.example.com

  • 포트 불일치

    • http://example.com

      http://example.com:8080

SOP

  • 동일 출처 정책(Same-Origin Policy, SOP)
  • 어떤 출처에서 불러온 문서나 스크립트가 다른 출처에서 가져온 리소스와 상호작용하는 것을 제한하는 중요한 보안 방식
  • 잠재적으로 해로울 수 있는 문서를 분리함으로써 공격받을 수 있는 경로를 줄일 수 있다.

CSRF

  • 사이트 간 요청 위조(Cross-Site Request Forgery, CSRF 또는 XSRF)
  • SOP가 등장하기 전에는 악성 웹사이트에서 사용자의 브라우저에 저장된 쿠키를 이용해 공격자가 원하는 요청을 보낼 수 있었다.
  • 이를 막기 위해 SOP를 따르는 브라우저는 다른 웹사이트로 요청을 보내는 경우를 감지해 요청을 막았다.
    • 이미지나 script 태그, CSS 등은 막지 않기에 추가 보안을 위하여 CSRF 토큰이나 transaction tickets을 이용한다.

CORS

  • 교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)
  • 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제

기능적 개요

  • 웹 브라우저에서 해당 정보를 읽는 것이 허용된 출처를 서버에서 설명할 수 있는 새로운 HTTP 헤더를 추가
    • HTTP 헤더: 클라이언트와 서버가 요청 또는 응답으로 부가적인 정보를 전송
      • 대소문자를 구분하지 않는 이름과 콜론 : 다음에 오는 값으로 구성
  • HTTP 요청 메서드에 대해, 브라우저가 요청을 OPTIONS 메서드로 프리플라이트하여 지원하는 메서드를 요청하고, 서버의 허가가 떨어지면 실제 요청을 보내도록 요구
    • HTTP 요청 메서드: GET을 제외한 HTTP 메서드
    • 프리플라이트: preflight, 사전 전달 또는 사전 요청

단순 요청

  • 가장 간단한 접근 제어 프로토콜은 Origin 헤더와 Access-Control-Allow-Origin을 사용하는 것이다.

https://foo.example의 웹 컨텐츠가  https://bar.other 도메인의 컨텐츠를 호출하는 상황을 다음 코드로 가정하자.

1
2
3
4
5
6
const xhr = new XMLHttpRequest();
const url = 'https://bar.other/resources/public-data/';

xhr.open('GET', url);
xhr.onreadystatechange = someHandler;
xhr.send();

서버로 보내는 요청은 다음과 같다.

1
2
3
4
5
6
7
8
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example

서버에서 받아온 응답은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

[…XML Data…]
  • Access-Control-Allow-Origin: *: 모든 도메인에서 접근할 수 있음을 의미
  • 만약 응답이 Access-Control-Allow-Origin: https://foo.example처럼 특정 도메인을 지정했다면 이외의 도메인은 cross-site 방식으로 리소스에 접근할 수 없다.

프리플라이트 요청

  • 서버 데이터에 영향을 미칠 수 있는 DELETE나 PUT 요청 또는 커스텀 헤더와 함께 보내지는 GET, HEAD, POST 요청은 프리플라이트 요청을 보낸다.
    • 안전하지 않은 요청의 조건
  • 프리플라이트 요청은 OPTIONS 메서드를 통해 다른 도메인의 리소스로 HTTP 요청을 보낸 뒤, 실제 요청이 전송하기에 안전한지 먼저 확인한다.

서버에 요청을 보내기 위해 다음과 같은 코드를 가정하자.

1
2
3
4
5
6
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://bar.other/resources/post-here/');
xhr.setRequestHeader('Ping-Other', 'pingpong');
xhr.setRequestHeader('Content-Type', 'application/xml');
xhr.onreadystatechange = handler;
xhr.send('<person><name>Arun</name></person>');

위 코드는 서버와의 첫 번째 통신으로 다음과 같은 preflight request 요청을 보낸다.

1
2
3
4
5
6
7
8
9
10
OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

요청에는 Origin, Access-Control-Request-Method, Access-Control-Request-Headers 헤더가 포함된다.

  • Access-Control-Request-Method: 실제 요청을 전송할 때 POST 메서드로 전송된다는 것을 알림
  • Access-Control-Request-Headers: 실제 요청을 전송 할 때 X-PINGOTHER 와 Content-Type 사용자 정의 헤더와 함께 전송된다는 것을 알림
    • 콤마로 구분한다.

서버는 다음과 같은 preflight response를 보낸다.

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive

응답에는 Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers가 포함된다.

  • Access-Control-Allow-Methods: 허용 가능한 메서드 목록
  • Access-Control-Allow-Headers: 허용 가능한 헤더 목록

실제 요청의 정보와 응답 헤더의 정보가 일치하지 않으면 브라우저는 실제 요청을 보내지 않는다.

요약

  • CORS는 다른 출처의 자원에 접근할 수 있도록 권한을 부여하도록 브라우저에 알려주는 체제이다.
  • 서버에 요청을 보낼때 헤더에는 Origin이 담기며 응답에는 접근 가능한 출처를 Access-Control-Allow-Origin 헤더에 담는다.
  • 서버 데이터에 영향을 미칠 수 있는 등 안전하지 않은 요청을 보낼 때에는 프리플라이트 요청을 먼저 보내 허용 가능한 메서드와 헤더 정보 접근 가능한 출처와 함께 받아온다.

참고 자료

This post is licensed under CC BY 4.0 by the author.