CORS에 대한 사실과 오해

Hansu Park·2023년 11월 19일
0

CORS는 cross origin resource sharing의 약자로
교차된 출처(Origin)의 자원을 공유할 수 있는 매커니즘(혹은 시스템)이다.

CORS에 관한 헤더 설정과 허용방법(모든 출처를 허용하는 방법..), XSS, CSRF을 막을 수 있다고 알고있었으나,

서브 도메인 여러 도메인을 포함하는 방법, XSS CSRF를 막을 수 없다는 것을 알게되어 CORS와 이들을 함께 설명하려고 한다.

CORS에 대해 설명하기 전 같은 출처에 대해서만 자원을 공유하도록 하는 정책인 SOP(Same Origin Policy)가 무엇인지와 왜 생겨나게 되었는지에 대해 알아보자.

SOP (Same Origin Policy)의 배경

SOP는 동일 출처에 대해서만 자원을 공유하도록 해주는 정책이다.

이는 1995년 Netscape에 의해 웹페이지에 스크립트 코드를 넣을 수 있게 해주는 "Javascript"가 등장한 이후로 발생가능한 스크립트를 이용한 공격들을 대처하기 위해 고안되었다.

추가로 SOP는 브라우저에 적용되는 보안 정책이다. 즉, Curl와 같은 명령어, PostMan과 같은 프로그램에서는 적용되지 않는데, 이는 위에서 언급한 것처럼 웹페이지에 심는 스크립트 공격을 대처하기 위해 존재하는 정책이기 때문이다.

CORS의 등장

기존에는 JSONP라는 script tag를 이용해 자원을 공유하는 트릭을 사용하였으나,
2009년부터 다른 출처에 대해 자원을 허용할 수 있는 CORS가 등장하게 되었다.

CORS

Origin

SOP, CORS에서는 Origin이라는 요소를 통하여 동일한 URL인지를 판단한다.

Origin은 scheme, host, port로 이루어져 있고
{scheme}://{host}:{port}로써 사용한다. 세 요소가 일치해야 같은 Origin으로 판단한다.

https://google.com:443의 경우
scheme: https
host: google.com
port: 443

이다.

※ https://cloud.google.com 과 같은 서브도메인은 다르게 판단한다.

동작 방식

Preflight

  1. 본 요청에서 사용할 메서드 헤더 정보를 명시한 사전 요청(Preflight)를 보낸다.
  2. 응답을 통해 정책을 위반하는지 확인한다.
  3. 확인이 된 경우 본 요청을 보낸다.

Simple Request
아래와 같은 조건에 모두 해당할 경우 Preflight 없이 요청만을 보낼 수 있고, 결과로 받은 응답 값을 통해 정책을 위반하는지를 확인한다.

조건

  • GET, HEAD, POST의 메서드인 경우
  • 자동 설정 헤더, CORS 허용 헤더만 사용한 경우
  • application/x-www-form-urlencoded, multipart/form-data, text/plain 의 컨텐츠 타입인 경우
  • XMLHttpRequest + EventListener이 아닌 경우
  • Readable Stream을 요청에 사용하지 않은 경우

관련 헤더 설정

헤더 설정을 통해 어떻게 CORS를 적용할 수 있는지에 대해 살펴보자.

Server (Response)

서버는 요청의 "Origin" 헤더를 통해 클라이언트의 URL을 식별하고

아래와 같은 헤더들을 설정하여 허용 여부를 표현할 수 있다.

  • Access-Control-Allow-Origin: 허용하고자 하는 출처를 설정할 수 있는 중요한 헤더이다.
    • */* 처럼 모든 출처를 허용할 수도 있고, (권장 X)
    • https://google.com:443 처럼 특정 출처를 허용할 수도 있다.
  • Access-Control-Allow-Credentials : 특정 조건(추후 설명)일 때, 응답을 클라이언트의 JS 코드로 사용(expose)할 수 있을지를 허용한다. 사전요청의 경우, 요청의 허용 여부를 나타낸다.
  • Access-Control-Expose-Headers : 액세스가 가능한 헤더들을 안내한다.
  • Access-Control-Max-Age : preflight 요청의 캐시 기간(초)를 안내한다.
  • Access-Control-Allow-Methods : 요청의 허용가능한 메서드 목록을 안내한다.
    • preflight를 위한 option header는 추가하지 않아도 된다.
  • Access-Control-Allow-Headers : 허용하는 (커스텀) 헤더의 목록

Client (Request)

  • Origin: 요청한 클라이언트의 헤더를 나타낸다. (이를 통해 CORS를 처리한다.)
  • Access-Control-Request-Method: 요청한 메서드를 명시한다.
  • Access-Control-Request-Headers: 요청에 사용된 헤더들을 명시한다.

기타 팁

서브도메인을 어떻게 허용할까? (참고)

  • https://*.google.com:443 처럼 서브 도메인을 허용하는 헤더는 작동하지 않는다.
  • 설정 파일에 정규표현식을 이용하여 검증하거나, 코드 단에저 제어할 수는 있다. (요청한 출처가 *.google.com에 포함되면 해당 출처를 허용해주는 방식)
  • 스프링 부트에서는 여러 출처를 allowedOriginPatterns로 허용해줄 수도 있다고 한다.
  • 본인은 다음과 같은 방법으로 해결하였다.
  1. properties 파일을 통해 허용할 도메인 목록 작성
  • 사용하는 서브 도메인을 모두 적는다.
  • 클라우드 서비스의 경우 사용하지 않는 서브도메인 하이재킹이 일어날 수 있기 때문이다. (참고 )
  1. 서버가 시작할 때, 도메인 목록을 HashSet 자료구조에 보관한다.
  2. 요청의 Origin을 HashSet과 비교하여 검증한다.

Credential (보안 증명)

서버의 Access-Control-Allow-Credentials: true + Access-Control-Allow-Origin 명시와 더불어

클라이언트에서의 withCredentials 설정을 통해 기존에 막혀있던 교차 출처간 인증 요청(쿠키, 세션, TLS 인증이 들어간 요청)을 허용할 수 있다.

  • GET 요청의 경우 클라이언트의 설정만 있을 경우 쿠키, 세션을 보낼 수 있다.
  • 하지만, 서버측 설정이 있어야 해당 응답의 내용을 사용(expose)할 수 있다.
  • 따라서, CSRF와 같은 쓰기 공격은 여전히 유효하다.
    (참고: 해당 링크의 답변의 heavi5ide, Pacerier의 답글)
  • XMLHttpRequest 의 경우 withCredentialstrue, false 로 쿠키 설정을 허용할 수 있고,

Fetch의 경우 Request.credentials

  1. same-origin: 동일 출처에서만 허용
  2. include: 다른 출처에서도 허용
  3. omit: 모든 출처에서 비허용
    로 설정할 수 있다.

SOP, CORS 정책으로 XSS, CSRF를 막을 수 있을까?

정답은 아니다. 물론 둘 다 아니다.

XSS의 경우는 어차피 허용된 도메인 내에 삽입된 스크립트로 하는 공격이기 때문에 출처에 영향을 크게 받지 않는다. (참고: 링크)

CSRF의 경우에도 SOP로가 읽기요청을 막는 것이지 쓰기 요청을 막는 것은 아니기 때문에 비밀번호를 변경하거나, 사용자의 권한을 바꾸는 등 쓰기 요청은 유효할 수 있다. (참고: 링크의 T.J. Crowder와 cifer의 답변)

참고

0개의 댓글