CORS

땡칠·2023년 5월 23일
2

CS

목록 보기
2/2
post-thumbnail

개요

CORS가 허용되거나 차단되는 과정은 이미 자료가 충분히 많다.
이 글에선 사용 목적과 구현 방식, 그 이유, 한계를 알아본다.

목적

CORS: Cross-Origin Resource Sharing

CORS는 교차(서로 다른) 출처 간 자원 공유를 위한 메커니즘이다.

중요한 점 하나는 자원 공유를 틀어막기 위한 기술이 아니라, 안전하게 풀기 위한 기술이라는 것.

브라우저는 사용자의 인터넷 환경을 따르기 때문에 다른 출처의 자원에 접근하는 것만으로도 위험할 수 있다.
따라서 브라우저는 Same-Origin Policy를 사용해 스크립트가 다른 출처의 리소스를 접근하지 못하게 막는다.
(사실 스크립트 외에도 몇 가지 더 있지만, 여기서는 스크립트만 다룬다)

구체적인 이야기는 뒤에서 해보자.

왜 이렇게 복잡하게 해야할까?

그냥 다 접근할 수 있게 열어두면 안될까?
CORS가 존재하지 않는다고 가정해보자.

악의적인 사이트 A는 사용자의 브라우저로 B사이트의 자원을 요청하고, 그 결과를 다시 전송받는 스크립트를 탑재했다.

(1) A 사이트에 접속하면, 클라이언트는 스크립트에 의해 (2) B 사이트에 접근한다.

(3) 스크립트는 (2)의 결과를 A 서버로 전송한다.

이 행위는 두 가지 입장에서 볼 수 있다.

  • 사용자 입장
    • 사용자 몰래 사용자의 브라우저로 특정 자원에 접근했다.
  • 서버 입장
    • 인가되지 않아야 할 악의적인 자원 접근이 특정 사용자의 권한으로 허용됐다.
    • 예를 들어, B가 우테코 사내 API라고 해보자.
      • 외부에서 사용자의 권한으로 사내 API를 호출할 가능성이 열린다.
        • 사용자가 코치였다면, 크루를 퇴출하는 API가 실행될 수 있다.
      • 사내 API 요청의 결과가 외부로 유출될 가능성이 열린다.
        • 사용자가 코치라면, 크루들의 정보가 외부로 유출될 수 있다.

이렇게 악의적인 요청은 어플리케이션에 따라서 큰 손해를 일으킬 수도 있다.
그래서 브라우저의 CORS 정책은 기본 Same-Origin으로 설정되어 있다.

Same-Origin Policy

같은 출처간의 접근만 허용한다.

‘같은 출처’라 함은, 호스트와 포트까지 같은 것을 의미한다.

반대로 서버에서 허용을 명시하지 않으면 다른 출처에서는 접근이 불가하다.

ex) localhost:8080 (웹 페이지) → localhost:9090 (서버)
서버가 별도로 명시하지 않았다면, 접근이 불가하다.

그럼 어떻게 접근해요?

현대의 웹 어플리케이션은 웹 클라이언트와 서버가 다른 주소에 있는 경우도 많다.
따라서 클라이언트는 접속한 사이트와 다른 주소로 요청을 보내야 한다.

이 경우 서버가 특정한 클라이언트(들)에 한해 요청을 허용해주기 위한 설정을 한다.

다음처럼 응답 헤더에 허용할 출처, 메서드 등을 적어주는 방식이다:

이를 해석해보면 다음과 같다

  • 모든 주소에서의 자원 접근을 허용한다.
    • https://localhost:443에서도, https://naver.com에서도, http://localhost:8080에서도 이 리소스에 접근이 가능하다.
  • GET, HEAD, POST… 등의 HTTP Method를 허용한다.
    • POST로도, GET으로도, HEAD로도 모두 접근 가능하다.

다시 말하지만, CORS는 무조건 틀어막는 것이 아니라 다른 출처에 안전하게 접근하기 위한 방법이다.

이미 접근된거 아닌가요?

이대로라면 그렇다.

이미 요청이 들어갔고, 그 응답으로 Access-Control-Allow-Origin 등 서버의 CORS 설정이 응답됐다.

서버는 소 잃고 외양간을 고쳐달라고 하는 셈이다.

이런 일을 막기 위해 브라우저는 스크립트를 통해 다른 출처에 보내는 요청을 조금 다르게 관리한다.

본 요청을 보내기 전, Preflight Request를 보낸다.

Preflight RequestOPTIONS 메서드 및 특정 헤더가 설정되어 날아간다.

이 요청은 GET, POST, DELETE 등 일반적인 Method와 구분되므로, 서버는 CORS 설정을 포함시켜 적절한 응답을 내려줄 수 있다.

그 응답이 바로 이전에 봤던 응답이다.

브라우저는 Preflight Request의 응답을 해석한 후, 문제없다고 판단한 후에서야 비로소 본 요청을 보낸다.

한계

클라이언트-사이드

CORS는 브라우저 단(클라이언트 측)에서 구현된 행위다.

따라서 CORS 정책을 지원하지 않거나 취약점이 있는 브라우저, 혹은 별도 클라이언트를 사용하면 Same-Origin이 아니라도 얼마든지 요청을 보내고 응답받을 수 있다.

CORS는 부가적인 보안 정책일 뿐, 악의적인 행위의 원천 차단은 불가능하다.

  • 별도의 클라이언트 postman을 사용해 CORS 설정과 관련없이 요청한 응답을 받았다:

보안을 CORS에 의존해선 안된다

우테코 동영상 서버에는 연극 영상이 있고, LMS에 탑재하기 위해 리소스를 공개했다.

하지만 연극 영상은 극비이기 때문에 LMS 외부로 노출되어선 안된다.

CORS 응답 헤더에 Access-Control-Allow-Origin: https://techcourse.woowahan.com을 싣는것으로 목적을 달성할 수 있을까?

당연하게도 답은 ‘도움되지 않는다’이다.

CORS의 Same-Origin Policy는 웹 브라우저에서 사용하는 정책일 뿐이다.

서버가 Preflight Request에 응답하는 내용은 단지 희망사항일 뿐이며, 클라이언트는 얼마든지 다르게 처리할 수 있다.

따라서 외부인도 별도의 클라이언트나 웹 페이지를 조금 조작하는 것만으로 ‘극비 동영상’에 접근할 수 있다.

이 목적을 위해선 다른 기술을 사용해야 한다.

다른 출처의 리소스 인용을 막아볼까?

마찬가지로 우테코 영상 서버에 연극 영상이 있다고 하자.

연극 영상의 리소스를 /secret-video.mov 로 공개하고, CORS Origin을 https://techcourse.woowahan.com 으로 한정했다.

LMS 페이지에 <video src="https://video-server/2023/secret-video.mov"/> 를 삽입한다.

연극이 LMS 페이지에 공개됐다.

일반적인 환경에서는 이 태그를 다른 출처에서 사용할 순 없겠지?

만만의 콩떡이다.

video, img,script 등 HTML 태그의 src는 CORS와 관계없다.

다시 말하지만, CORS의 Same-Origin Policy는 ‘스크립트’가 다른 출처에 접근하는 것을 막는다.

따라서 HTML 태그는 관리 대상이 아니며, 저 태그를 어디에 삽입해도 동영상이 잘 로드된다.

이렇게 만든 이유는 아쉽게도 아직 알아내지 못했다.

(누가 알려주세요..!)
본인이 추측하기로는 다음과 같다.

  • img, video, audio 등은 ‘GET’만 가능할 뿐, 이걸 사용해 원하는 요청을 전송하기엔 무리가 있다.
  • script 태그를 사용해 스크립트를 로드해도, 동적 요청 전송은 어차피 CORS 제약이 반영된다.

오히려 이것들에도 CORS를 적용하면, ‘미디어 표시’라는 웹의 핵심 기능에 큰 제약이 생긴다.

따라서 보안 위협을 막는 효과는 미미하고, 제약사항은 커지니 하나를 포기하거나, 다른 방식으로 보완하지 않았을까 추측한다.

마무리

이 글은 이해를 돕기 위해 생략된 내용들이 많다.

  • CORS를 지원하지 않는 브라우저, API도 있다는 것
  • 스크립트 외에 다른 방식(폰트, 캔버스에 그리는 이미지) 등도 CORS가 적용된다는 것
  • Preflight 성공 여부와 CORS 검사는 별도라는 것
  • 교차 출처라고 꼭 Preflight Request가 날아가지는 않는다는 것
  • Origin vs Site
    • 별도로 비교하지 않았다.
  • XSS 관련

References

profile
자신을 찾아 개선하는 중

2개의 댓글

comment-user-thumbnail
2023년 6월 3일

잘 읽었습니다~

1개의 답글