CORS

소밍·2023년 3월 6일
0
post-thumbnail

<img>, <video>, <script>, <link> 태그의 경우 기본적으로 cross-origin 정책을 지원하지만 XMLHttpRequest, Fetch API 스크립트의 경우 same-origin 정책을 따른다. 자바스크립트에서의 요청은 기본적으로 서로 다른 도메인에 대한 요청을 보안상 제한한다. 브라우저는 기본으로 하나의 서버 연결만 허용되도록 설정되어 있기 때문이다.


1. Origin, 출처 ?

protocol + Host + Port

console.log(location.origin); 으로 현 로케이션의 출처를 파악할 수 있다.

1-1. URL의 구성

  • protocol (http, https)
  • Host (사이트 도메인)
  • Port (포트 번호; 기본값 80)
  • Path (사이트 내부 경로)
  • QueryString (요청 key와 value값)
  • Fragment (해시태그)

1-2. IE 경우

  • 포트 판단을 하지 않아 포트가 달라도 같은 출처로 인식되는 문제가 있다.
  • 양쪽 도메인 모두 높음 단계의 보안 수준을 가지고 있는 경우 SOP를 적용하지 않는다.



2. SOP 동일 출처 정책 (Same-Origin Policy)

동일 출처(Same-Origin) 서버에 있는 리소스는 자유로이 가져올수 있지만,
다른 출처(Cross-Origin) 서버에 있는 리소스 사용을 제한하는 보안방식이다.
만일 SOP가 없을 경우 해커가 개인정보를 가로채는 문제 등이 발생할 수 있다.
SOP 정책으로 동일하지 않은 출처의 스크립트가 실행되지 않도록 브라우저에서 사전에 방지할 수 있다. 문제는 우리는 타 출처의 리소스가 필요할 때가 있다는 것, 그리하여 웹 브라우저에 CORS라는 표준이 마련되었다.

[ 출처를 비교하는 로직은 서버에 구현된 스펙이 아닌 브라우저에 구현된 스펙 ]

서버는 리소스 요청에 대해 정상 응답을 하지만 브라우저가 서버의 응답을 분석하여 동일 출처가 아닐 경우 받을 수 없도록 차단하기 때문에 에러를 보여준다.
(브라우저가 정책으로 차단을 한다는 말은, 브라우저를 통하지 않고 서버 간에 통신을 할때는 정책이 적용되지 않는다는 말과 같다.
즉, 클라이언트 단 코드에서 API 요청을 하는게 아니라, 서버 단 코드에서 다른 출처의 서버로 API 요청을 하면 CORS 에러로부터 자유로워 진다.
이를 이용한 것이 프록시 서버이다.)



3. CORS 교차 출처 리소스 공유 (Cross-Origin Resource Sharing)

추가 HTTP 헤더를 사용하여 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 리소스에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다. SOP 정책을 위반하더라도 CORS 정책에 따르면 다른 출처의 리소스라도 허용한다.



4. CORS 접근제어 세가지 시나리오

4-1. 단순 요청(Simple Request)

  • prefilght 요청 없이 바로 실제 요청을 보내는 즉시
    해당 요청이 cross origin 인지 확인한다.
  • 메서드
    • GET, POST, HEAD
  • 헤더
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type
  • Content-Type 헤더는 아래의 값들만 허용
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

4-2. 사전 요청 (Preflight Request)

- Preflight를 보내면 실질적으로 두번 요청이 보내진다. (사전 요청, 실제 요청)
- OPTIONS 메서드를 통해 다른 도메인의 리소스에 요청 가능한지 확인한다.
- 요청 가능하다면 실제 요청을 보낸다.

** cross-origin 요청은 유저 데이터에 영향을 줄 수 있기 때문에 사전 요청을 진행하는 것 ! **

CORS에서 사전 요청은 OPTIONS 메서드를 통해 전송되므로 요청을 보낼 수 있는 경우라면 서버가 응답한다.

즉, 사전 요청은 본격적인 교차 출처 HTTP 요청 전에 서버 측에서 그 요청의 메서드와 헤더에 대해 인식하고 있는지를 체크하는 것이다.


* 굳이 왜 사전 요청을 보내야하지 ?
CORS spec이 생기기 이전에 만들어진 서버들은 브라우저의 SOP(same origin policy) request만 가능하다는 가정하에 만들어졌다. 이런 서버들은 cross-site request에 대한 security mechanism이 없는데 CORS로 인해 cross-site request가 가능해지면서 보안적으로 문제가 생길 수 있었다. 이를 보호하기 위해 CORS spec에 사전 요청을 포함한 것이다. 즉, 사전 요청으로 서버가 CORS를 인식하고 핸들할 수 있는지 먼저 확인을 함으로써 CORS를 인식하지 못하는 서버들을 보호할 수 있는 것이다.

* Preflight Request

  • Origin : 요청 출처
  • Access-Control-Request-Method : 실제 요청의 메서드
  • Access-Control-Request-Header : 실제 요청의 헤더

* Preflight Response

  • Access-Control-Allow-Origin : 서버 측 허가 출처
  • Access-Control-Allow-Method : 서버 측 허가 메서드
  • Access-Control-Allow-Headers : 서버 측 허가 헤더
  • Access-Control-Max-Age : preflight request 요청 결과를 캐시할 수 있는 시간
    (응답 코드는 200대, 응답 바디는 비어있는 것이 좋다.)

사전 요청을 사용할 경우 실제 요청에 걸리는 시간이 늘어나게 되어 어플리케이션 성능에 영향을 미친다는 단점이 있다.
특히 수행하는 API 호출 수가 많으면 많을 수록 사전 요청으로 인해 서버 요청을 배로 보내게 되니 비용 적인 측면에서 좋지 않다.
따라서 브라우저 캐시(Cache) Visit Website 를 이용해 Access-Control-Max-Age 헤더에 캐시될 시간을 명시해 주면, 이 Preflight 요청을 캐싱 시켜 최적화를 시켜줄 수 있다.

4-3. 인증정보 포함 요청(Credentialed Request)

  • 인증 관련 헤더를 포함할 때 사용하는 요청이다.
  • HTTP cookies 와 HTTP Authentication 정보를 인식한다.
  • 브라우저는 Access-Control-Allow-Credentials: true 헤더가 없는 응답을 거부하며 호출된 웹 컨텐츠에 응답을 제공하지 않는다.

* 클라이언트측

  • credentials
    • same-origin(기본값) : 같은 출처 간 요청에만 인증 정보를 담을 수 있다.
    • include : 모든 요청에 인증 정보를 담을 수 있다.
    • omit : 모든 요청에 인증 정보를 담지 않는다.

* 서버측

  • Access-Control-Allow-Credentails : true
  • 인증 관련 헤더를 포함하고 있는데 Access-Control-Allow-Origin을 *로 설정할 경우 요청 실패.



5. CORS 해결방법

  • Access-Control-Allow-Origin 헤더 직접 설정해주기
  • 프론트 프록시 서버 설정 (개발환경)
  • 스프링 부트 이용하기



6. 취약한 CORS 구성 예방 및 완화 가이드

6-1. Access-Control-Allow-Origin : * 금지


6-2. Origin 요청 헤더 값 그대로 사용 금지

다른 출처끼리 쿠키 통신할 때 클라이언트에서 withCredentials 옵션 활성화해야하는데
Access-Control-Allow-Origin 헤더에 와일드 카드(*)를 사용할 수 없다.

→ 이 때 라우터에 들어온 요청 데이터에 request 객체의 origin 헤더값을 가져와
그대로 Access-Control-Allow-Origin에 넣는 방식 금지한다.

app.get('/users', (req, res) => {
    res.header("Access-Control-Allow-Origin", req.headers.origin);
    // ...
    }
)

6-3. Null 출처 허용 금지

공격자는 샌드박스 처리된 iframe을 이용해 Origin 헤더에 NULL을 지정해 요청할 수 있으므로 NULL 출처를 리소스 접근 허용 목록에 포함시키지 말아야 한다.


6-4. 신뢰할 수 있는 도메인 관리 및 안전한 검증

  • CORS Origin을 설정할때 정규식으로 처리할 경우 완벽한 검증이 필요하다.
  • whitelist를 사용하여 허용할 출처를 저장해놓고 관리하는 것이 좋다.
    이 경우 클라이언트에서 미리 origin 헤더값을 위조하더라도
    브라우저에서 감지하여 원천 차단하기 때문에 서버의 CORS가 뚫리지 않는다.


CORS 시나리오 작동 체험 사이트



참고자료

MDN - SOP
MDN - CORS
MDN - 사전요청
10분 테코톡 - 나봄의 CORS
개인블로그
개인블로그
기타 참고 자료

profile
생각이 길면 용기는 사라진다.

0개의 댓글