[웹] 교차 출처 리소스 공유 (CORS)

SOP와 CORS

단일 출처 정책(SOP, Single Origin Policy)이란 웹 애플리케이션의 클라이언트(예: 웹 브라우저)가 한 출처의 스크립트는 동일한 출처의 페이지의 데이터에만 접근 가능하도록(다른 출처의 페이지의 데이터에는 접근 불가능하도록)하는 웹 애플리케이션 보안 모델이다. 여기서 출처(origin)란 URI 스킴, 호스트명, 포트 번호의 조합으로 정의된다. SOP는 서로 다른 출처(교차 출처)에 대한 요청을 허용하지 않으며 한 페이지의 악성 스크립트가 다른 웹 페이지의 데이터에 접근할 수 없도록 한다. SOP는 오직 스크립트에 대해서만 적용된다. 특히 도큐먼트 내에서 발생한 XMLHttpRequest 호출에 적용된다. 이미지, CSS 및 동적으로 로드된 스크립트에 대해서는 적용되지 않는다.

교차 출처 리소스 공유(CORS, Cross-Origin Resource Sharing)란 웹 애플리케이션 클라이언트가 리소스를 제공 받은 출처 외 다른 출처에서 리소스를 제한적으로 요청할 수 있도록 하는 HTTP 헤더 기반 메커니즘이다. SOP에 의해 교차 출처(cross-origin) 요청은 기본적으로 허용되지 않는다. 기본적으로 한 웹 사이트가 XHR 또는 fetch 사용하여 다른 사이트에 HTTP 요청을 할 수 없도록 설계되어 있다. 이로 인해 다른 사이트의 스크립트가 사용자를 대신하여 작동하거나 허가 없이 다른 사이트의 리소스에 액세스하는 것을 방지할 수 있다. CORS는 SOP에 의해 제한된 교차 출처 리소스 요청을 제어하는 방법이다. 즉, CORS는 SOP를 위반하는 교차 출처 요청을 허용해도 안전한지 결정하는 데 도움을 주는 방법으로 볼 수 있다. CORS는 현재 출처와는 다른 출처 중 접근을 허용할 출처를 지정함으로써 교차 출처 접근을 할 수 있도록 한다. CORS와 관련된 HTTP 요청 및 응답 헤더를 기반으로 스크립트를 제공한 출처와 다른 출처 간 리소스를 공유(서로 다른 도메인 간 리소스 요청)를 허용할지 결정한다. CORS를 통해 교차 출처 요청을 제어하는 주체는 스크립트의 요청 대상 출처이다.


웹 클라이언트와 웹 서버의 통신 과정

CORS를 적용하기 전 클라이언트와 서버 간 통신 시 동일 출처가 허용되거나 금지되는 과정은 다음과 같다.

  1. 클라이언트는 웹 서버 A(도메인 A)에 웹 도큐먼트 리소스와 관련 스크립트를 요청한다.
  2. 클라이언트는 스크립트 코드를 실행한다.

    2-1. 동일한 출처인 웹 서버 A(도메인 A)에 대해 또다른 XMLHttpRequest 요청을 보낸다. 이 때의 요청은 동일 출처 요청(same-origin request)이며 SOP에 의해 허용된다. 웹 서버 A(도메인 A)는 요청에 대한 응답을 클라이언트에게 정상적으로 보낸다.

    2-2. 동일한 출처가 아닌 웹 서버 B(도메인 B)에 대해 또다른 XMLHttpRequest 요청을 보낸다. 이 때의 요청은 교차 출처 요청(cross-origin request)이며 SOP에 의해 허용되지 않는다.


CORS를 적용한 후 클라이언트와 서버 간 통신 시 교차 출처가 허용되는 과정은 다음과 같다.

  1. 클라이언트는 웹 서버 A(도메인 A)에 웹 도큐먼트 리소스와 관련 스크립트를 요청한다.
  2. 클라이언트는 스크립트 코드를 실행한다.
  3. 동일한 출처가 아닌 웹 서버 B(도메인 B)에 대해 또다른 XMLHttpRequest 요청을 보낸다. 이때 HTTP 요청 및 응답 헤더에 CORS를 제어하기 위한 정보가 포함되어 있다. 이 때의 요청은 교차 출처 요청(cross-origin request)이지만 CORS에 의해 허용된다.


CORS의 동작 원리

CORS는 스크립트가 원본 출처(스크립트를 제공한 출처)와는 다른 출처에 요청을 보내는 것을 허용하기 위한 방법이다. 접근 제어는 요청을 받는 서버 측에서 결정한다.

CORS를 통해 접근 제어를 수행하는 방법에는 일반적인 HTTP 요청과 프리플라이트(preflight) 요청 등이 있다.


일반적인 요청을 통한 CORS

스크립트는 서로 다른 출처에 요청을 보낼 때 요청 헤더에 Origin 헤더를 추가한다. 헤더 값으로 스크립트를 제공한 출처(도메인)를 지정한다.

요청을 받은 서버는 응답 헤더에 Access-Control-Allow-Origin 헤더를 추가한다. 헤더 값에 따라 CORS가 허용 여부가 달라진다. 헤더 값이 *일 경우 서버의 리소스는 모든 출처에서 접근 가능하며 CORS가 허용된다. 따라서 스크립트는 제한 없이 해당 서버에 리소스를 요청한 후 응답으로 받을 수 있다. 헤더 값이 *가 아닌 https://foo.example와 같이 특정 도메인일 경우 서버의 리소스는 모든 출처에서 접근 가능하지 않으며 CORS가 허용되지 않는다. 따라서 해당 도메인의 스크립트만 해당 서버에 리소스를 요청한 후 응답으로 받을 수 있다.


프리플라이트 요청을 통한 CORS

스크립트는 서로 다른 출처에 요청을 보낼 때 OPTIONS 메서드를 사용하여 실제 요청을 보내는 것이 안전한지 확인한다. OPTIONS 메서드를 통한 요청 시 Origin 헤더 뿐만 아니라 Access-Control-Request-Method, Access-Control-Request-Headers 헤더를 추가할 수 있다.

요청을 받은 서버는 응답 헤더에 Access-Control-Allow-Origin 헤더를 추가한다. 값에 따른 CORS 허용 여부는 단순 요청과 동일하다.

프리플라이트 요청에 대한 응답 헤더에는 Access-Control-Allow-Origin 뿐만 아니라 Access-Control-Allow-Methods, Access-Control-Request-Headers, Access-Control-Max-Age 헤더가 포함될 수 있다. 이러한 헤더는 접근 제어에 대한 세부적인 설정을 위해 사용된다.

이 외에 크레덴셜을 포함한 요청을 통해 CORS를 허용할 수도 있다.


CORS가 문제가 되는 상황과 해결 방법

CORS가 허용되지 않아 외부 서비스로의 정상적인 웹 요청을 보내지 못하는 경우 브라우저에서 다음과 같은 에러가 발생할 수 있다.

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://some-url-here. (Reason: additional information here).


이 경우 해당 리소스 출처(https://some-url-here)인 웹 서버의 Access-Control-Allow-Origin 응답 헤더에 스크립트 리소스 출처(도메인)를 값으로 추가한다. CORS를 허용하기 위해 Access-Control-Allow-Origin 응답 헤더의 값으로 *를 사용하는 것은 보안상 권장되지 않으며 인증 및 허가된 출처만 접근 가능하도록 하는 것이 좋다.

외부 API 서비스가 CORS를 허용하지 않는 경우에는 위 방법을 적용하여 해결할 수 없다. 해당 API 서비스를 제공하는 웹 서버가 어떤 출처에 대해 응답을 허용할지 알 수 없기 떄문이다. CORS를 허용하지 않는 것이 의도된 서버는 Access-Control-Allow-Origin 헤더 값이 지정되어 있지 않을 것이다. 이 경우 CORS를 허용하도록 만들기 위해 프록시(proxy) 서버를 사용할 수 있다. 프록시 서버가 클라이언트와 리소스 출처 간의 통신을 중개하며 클라이언트는 직접 리소스 출처와 통신하지 않는다.

위 문제는 웹 브라우저가 CORS를 허용하지 않는 웹 서버에 직접 요청을 하기 때문에 교차 출처 리소스에 대한 접근이 불가능한 것이 원인이다. 따라서 CORS를 허용하는 프록시 서버를 만들고 프록시 서버가 해당 요청을 웹 서버에 전달하여 요청한 후 응답을 받아 다시 클라이언트에 전달하는 대리자 역할을 하도록 하면 된다. 프록시 서버가 웹 서버의 응답 결과를 사용하여 응답 메시지를 구성하고 Access-Control-Allow-Origin 응답 헤더를 추가하여 응답하게 함으로써 CORS를 허용하게 만들 수 있다.

프록시 서버는 Mixed Content 에러를 해결하기 위한 방법이기도 하다. 클라이언트가 웹 서버와 TLS 보안 프로토콜로 암호화된 HTTPS 통신을 통해 콘텐츠를 제공 받은 경우, HTTPS 페이지에 HTTP를 사용하여 가져온 콘텐츠가 포함되어 있으면 이를 Mixed Content 페이지라고 한다. 이러한 페이지는 부분적으로만 암호화된 것이므로 공격자가 암호화되지 않은 콘텐츠에 접근할 수 있어 안전하지 않다. 이 경우 프록시 서버를 사용하여 클라이언트는 프록시 서버와 항상 HTTPS 통신을 하도록 하고, 프록시 서버는 웹 서버와 HTTPS 또는 HTTP로 통신할 수 있게 함으로써 프록시 서버가 클라이언트 대신 웹 서버로부터 암호화되지 않은 통신으로 콘텐츠를 응답 받고 이를 클라이언트에게 암호화된 통신으로 전달할 수 있다.

CORS 프록시 서버는 보안 측면에서 안전하게 사용되어야 한다. URL 파라미터나 요청 헤더로 전달되는 API 서비스 키, 아이디 및 비밀번호 등의 개인 정보는 암호화되지 않으면 유출 가능성이 있다. 따라서 운영 환경에서 동작하는 웹 애플리케이션 서비스의 CORS 문제를 해결하기 위해 사용하려는 프록시 서버는 공개 호스팅된 서버가 아니며 방화벽에 의해 보호받을 수 있는 프라이빗 네트워크 내에 존재하도록 구성해야 한다.


참고

Categories: , ,

Updated:

Comments