✏️TIL: CORS

yeeun lee·2020년 7월 13일
2

공부 목적으로 작성한 게시물이며, 아래 자세하고 정성스럽게 정리되어 있는 글을 참고했습니다✍🏻

CORS는 왜 이렇게 우리를 힘들게 하는걸까?
CORS 맨날 봐도 모르겠어
세상의 모든 지식
CORS 개념과 간단한 XSS ,CSRF 소개
위키백과 & MDN


1. CORS?

교차 출처 리소스 공유(Cross-origin resource sharing, CORS), 교차 출처 자원 공유는 웹 페이지 상의 제한된 리소스를 최초 자원이 서비스된 도메인 밖의 다른 도메인으로부터 요청할 수 있게 허용하는 구조다.

  • 말 그대로 다른 서버의 리소스를 사용할 수 있는 것을 의미한다.
  • 만약 서버에서 내 웹을 허용(Access-Control-Allow-Origin: blah blah)만 해준다면, 접근이 가능하다
  • CORS는 2012년 이후에 출시된 거의 모든 브라우저 버전에 탑재된 보안 기능이다.

Cross

위키 백과에서 cross를 한국어로 직역했을 때 교차라는 단어를 사용했는데, 의미가 와닿지 않는 것 같다. 참고한 블로그에서는 '다른' 출처라는 말로 사용했다. 즉 origin이 다른 resource를 가져올 때 정책 위반 여부를 체크하는 것으로 이해하면 된다.

Origin

출처(origin)는 url의 구성 요소 중 protocol / host / port를 말한다. 서버의 위치를 찾아가기 위해 가장 기본적인 것 들이다. 개발자 도구 console에서 origin을 확인할 수도 있다.

console.log(location.origin);
// result : https://velog.io

출처에서 포트 번호는 생략이 가능한데, 웹에서 사용하는 http, https 프로토콜은 포트가 비어있을 경우 기본적으로 포트 번호가 80이라고 가정하기 때문이다.

- SOP(Same-Origin Policy)

Same-Origin만 요청, 응답이 가능하다는 것이다. 이때 Same-Origin은 프로토콜 (e.g. http) , 호스트명, 포트 가 같다는 것을 의미한다. 프로토콜이 http일때는 80 포트를, https 를 쓰면 443 포트를 쓰니 포트까지 같아야한다.

2. 사용해보기

다른 출처의 애플리케이션이 통신할 때 아무런 제약이 없으면, CSRF(Cross-Site Request Forgery)나 XSS(Cross-Site Scripting)를 사용해 정보를 탈취하는게 너무나 쉬워진다. cors를 배워서 보안을 강화하거나, 오류가 났을 때 해결하는 방법을 찾을 수 있도록 해보자.

판단하는 side?

출처를 비교하는 로직은 서버가 아닌 브라우저에 구현되어 있는 스펙이다. 서버에서 출처를 지정해놓지 않은 상황(예를 들어 *)이면 서버는 정상적으로 응답을 하게 되고, 브라우저가 응답을 분석해서 정책 위반 여부를 판단해 응답을 사용하거나 버리는 판단을 한다.

동작 원리

기본적으로 웹 클라이언트 어플리케이션이 다른 출처의 리소스를 요청할 때는 HTTP 프로토콜을 사용하여 요청을 보내게 되는데, 이때 브라우저는 요청 헤더에 Origin이라는 필드에 요청을 보내는 출처를 함께 담아보낸다.

Origin: https://velog.io/write?id=da8de91d-a923-49d4-8f2f-f34efe0eea71

이후 서버가 이 요청에 대한 응답을 할 때 응답 헤더의 Access-Control-Allow-Origin이라는 값에 “이 리소스를 접근하는 것이 허용된 출처”를 보내준다.

벨로그에서 개발자도구에 들어가 API를 받아보면, 아래 이미지와 같이 headers에 Access-Control-Allow-Origin이라는 키값이 있다.

이후 응답을 받은 브라우저는 자신이 보냈던 요청의 Origin과 서버가 보내준 응답의 Access-Control-Allow-Origin을 비교해본 후 이 응답이 유효한 응답인지 아닌지를 결정한다.

위를 보면 클라이언트가 요청한 Origin이 서버에서 허용하는 출처임을 알 수 있다. CORS는 세 가지 시나리오로 작동하기 때문에, 에러를 고칠 때는 아래 내용을 참고하는 것이 좋다.

- Preflight Request

프론트에서 fetch를 할 때, 서버에 예비 요청(OPTIONS 메소드)을 보내고, 클라이언트가 요청한 origin과 서버가 허용하는 origin을 확인한다. 서버 응답의 허용 정책이 안전하다고 판단되면, 브라우저는 다시 본 요청을 보내고 javascript에 response를 넘겨준다.

- Simple Request

prefligt request에서 예비 요청 없이 본 요청만 주고 받고 브라우저가 CORS 정책 위반 여부를 검사하는 것이다. 그런데 아래 조건을 만족해야 prefligt request를 생략할 수 있기 때문에, 사실상 simple request를 사용하는 것은 쉽지 않다.

로그인 토큰을 위해 엄청 많이 쓰는 Authorization이 헤더에 포함되어 있지 않다. Content-Type도 application/json을 주로 사용하는데 허용되는 데이터 형식이 너무 적다.

  • 요청의 메소드는 GET, HEAD, POST 중 하나여야 한다.
  • Accept, Accept-Language, Content-Language, Content-Type, DPR, Downlink, Save-Data, Viewport-Width, Width를 제외한 헤더를 사용하면 안된다.
  • 만약 Content-Type를 사용하는 경우에는 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용된다.

- Credentialed Request

다른 출처 간 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방법이다. request에 opiton값을 주는 것인데, fetch를 할 때 옵션에 credentials: 'include'를 주면 다음 두 가지 조건을 추가함으로써 정책 위반 여부를 확인하는 규칙이 엄격해진다.

  • Access-Control-Allow-Origin에는 *를 사용할 수 없으며, 명시적인 URL이어야한다.
  • 응답 헤더에는 반드시 Allow-Control-Allow-Credentials: true가 존재해야한다.

3. 미들웨어

CORS와 무슨 상관인가

CORS 정책 위반으로 인한 문제를 해결하는 가장 대표적인 방법은, 그냥 정석대로 서버에서 Access-Control-Allow-Origin 헤더에 알맞은 값을 세팅해주는 것이다.

복잡한 헤더 세팅은 불편하기 때문에 소스 코드 내에서 응답 미들웨어 등을 사용하여 세팅하는 것이 편하다. Spring, Express, Django와 같이 이름있는 백엔드 프레임워크의 경우에는 모두 CORS 관련 설정을 위한 세팅이나 미들웨어 라이브러리를 제공하고 있다.


미들웨어는 어플리케이션에 들어오는 HTTP 요청들을 필터링하는 데에 사용된다. 예를 들면 로그인에 관련된 처리가 있다. 어떤 사람이 로그인이 된 채로 HTTP 요청을 보내면 문제없이 접속할 수 없지만, 로그인이 되어 있지 않은 요청이 온다면 Middleware는 로그인 페이지로 리다이렉션하는 작업을 수행할 수 있다.

초기세팅

위코드에서 프로젝트 세팅을 할 때 django-cors-headers를 다운받고, installed app에 corsheaders & middleware에 corsheaders를 추가한 것이 생각났다.

잘 모르고 쓴거긴 하지만 origin이나 credential option도 설정했었고, 어떤 method와 header를 허용할 것인지도 지정을 해놓았었다.

pip install django-cors-headers

# settings.py
INSTALLED_APPS = [
...
		'django.contrib.staticfiles',
		
]
MIDDLEWARE = [
	...
		'corsheaders.middleware.CorsMiddleware',
	...
]

##CORS
CORS_ORIGIN_ALLOW_ALL=True
CORS_ALLOW_CREDENTIALS = True

CORS_ALLOW_METHODS = (
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
)

CORS_ALLOW_HEADERS = (
    'accept',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
#만약 허용해야할 추가적인 헤더키가 있다면?(사용자정의 키) 여기에 추가하면 된다.
)

4. CORS가 없을 때 문제점

XSS(Cross-Site Scripting)

사이트 간 스크립팅은 웹 애플리케이션에서 많이 나타나는 취약점의 하나로 웹사이트 관리자가 아닌 이가 웹 페이지에 악성 스크립트를 삽입할 수 있는 취약점이다. 주로 여러 사용자가 보게 되는 전자 게시판에 악성 스크립트가 담긴 글을 올리는 형태로 이루어진다.

웹 애플리케이션이 사용자로부터 입력 받은 값을 제대로 검사하지 않고 사용할 경우 나타난다.

- Reflected XSS

where the malicious script comes from the current HTTP request.
웹 페이지에서 직접 사용되는 URL의 비보안에 의해 발생하는 취약점이다. 예를 들어 검색창의 쿼리가 화면에 노출되는 API가 있다고 하자. 이 때 검색창에 script를 수행하는 query를 넣게 되면 완전히 다른 창으로 이동해버린다.

이렇게 쿼리가 노출되는 사이트는 url을 그대로 복사해서 피싱 사이트로 이동하는 script를 url로 만들고, 사람들이 앞 부분만 봤을 때 우리 사이트인줄 속여서 사이트로 유도할 수도 있는 것이다.

https://xss-game.appspot.com/level1

https://xss-game.appspot.com/level1/frame?query=
<script>window.open("https://www.google.com/search?q=apple")</script>

- Stored XSS

where the malicious script comes from the website's database.
검수되지 않은 사용자 입력으로 데이터베이스에 직접 저장되어, 다른 사용자에게 표시된다.

- DOM-based XSS

where the vulnerability exists in client-side code rather than server-side code.
reflected XSS와 비슷하지만 서버보다는 클아이언트 사이드의 취약점으로 생긴다.

CSRF(Cross-Site Request Forgery)

CSRF는 악의적인 웹사이트, 전자 메일, 블로그, 인스턴트 메시지 또는 프로그램으로 인해 사용자의 웹 브라우저가 사용자가 인증 된 다른 신뢰할 수 있는 사이트에서 원치 않는 작업을 수행 할 때 발생하는 공격 유형이다.

이 취약점은 브라우저가 세션 쿠키, IP주소 또는 각 요청과 유사한 인증 리소스를 자동으로 보내는 경우에 발생 할 수 있다.

profile
이사간 블로그: yenilee.github.io

0개의 댓글