CORS
웹 개발을 하게 되면 한번씩은 다 겪는 에러이자 골머리를 썩게 만드는 부분이다.
이 CORS라는게 당최 뭔지 문제가 발생 했을 때 어떻게 해결하면 되는지에 대해서 오늘은 알아볼 생각이다.
CORS란?
먼저 CORS는 Cross-origin-resource-sharing의 약자이다. 직역하자면 엇갈린 출처 리소스 공유 (정책)인데 여기서 엇갈린 출처라는게 중요하다.
여기서 말하는 출처란 이런 것이다.
우리가 어떤 사이트에 접속할 때 우리는 URL을 통해서 접속하게 되는데 가령 https://naver.com/blog?id=test 라는 URL을 통해서 접속한다고 한다면 여기서 출처는 프로토콜인 https와 naver.com인 사이트 도메인, 그리고 대부분 443이나 80이라서 자주 생략되는 포트번호를 뜻한다.
우리가 특정 사이트에 접속하였다는것은 그 사이트가 속한 출처로부터 페이지나 스크립트, 즉 리소스를 받아오는 것이다. 그런데 이 리소스가 동일 출처에서 온게 아니라면 공유 할 수가 없다.
가령 https://naver.com에 접속했는데 https://daum.net에서 가져온 리소스를 사용할 수 없다는 말이다. 이건 서버가 아니라 페이지를 열람하는 브라우저에서 막는 것이다.
하지만 인터넷을 쓰다보면 다른 출처에 있는 리소스를 사용하는 경우가 있는데 이런 경우까지 다 막아버리면 아무것도 할수가 없다. 그래서 웹에서 리소스를 갖고오고 사용하는 것은 기본적으로 출처 정책을 따른다.
출처 정책의 차이
이 출처 정책은 태그 별 함수별로 다른데, 세부 내용은 아래와 같다.
- 교차 출처 지원하는 태그의 경우 - CORS 정책에 따른 Block 발생하지 않음
- img나 video, script 태그의 src, link는 기본적으로 교차 출처 사용이 가능하기 때문에 브라우저에서 차단이 발생하지 않는다.
- XMLHttpRequest, Fetch API 스크립트 - CORS 정책에 따른 Block 발생함
- 다른 도메인 소스에 대한 ajax 청이나 다른 도메인 폰트 사용시 차단 된다
- 기본적으로 동일 출처를 사용해야한다.
2번과 같은 경우에서는 전체를 다 막아버리는데 그럼 어떻게 해야하는가? 이럴때 필요한게 CORS에 대한 정책이다. 다른 출처에서 갖고 왔을 지라도 CORS 정책을 준수한다면 허용이 가능하다.
CORS 정채의 작동 방식
CORS 정책은 일반적으로 3가지로 작동한다.
- 예비요청에 의한 CORS
- 실제 요청전 OPTION 요청에 의해 해당 서버로 요청을 보내는데, Origin 헤더에 출처를 넣고, Access-Control-Request-Method 헤더에 실제 요청에 사용할 메소드를 설정하고 Access-Control-Request-Headers 헤더에 실제 요청에 사용할 헤더들을 설정한다.
- Access-Control-Allow-Origin 헤더에 허용되는 Origin들의 목록을 설정한다. Access-Control-Allow-Methods 헤더에 허용되는 메소드들의 목록을 설정한다. Access-Control-Allow-Headers 헤더에 허용되는 헤더들의 목록을설정한다. Access-Control-Max-Age 헤더에 해당 예비 요청이 브라우저에 캐시 될 수 있는 시간을 초 단위로 설정한다.
- 브라우저에서 보낸요청과 응답 정책을 비교하여, 확인 후 요청을 보낸다.
- 서버가 응답하면 데이터를 브라우저에서 처리한다.
- 단순 요청
- 이 경우 예비 요청을 생략한다. 그렇기 때문에 본 요청에 대한 응답에 서버가 Access-Control-Allow-Origin 헤더를 달아서 보낸다.
- 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일때만 해당된다.
- 인증된 요청
- 자격 인증 정보를 실어 요청할때를 말한다.
- 클라이언트에서 credentials 옵션에 3가지 값을 사용하여 지정가능하다 : same-origin, include, omit
- 이러한 요청에서 응답시 서버에서 헤더는 아래와 같이 사용할수있다.
응답 헤더의Access-Control-Allow-Credentials 항목을 true로 설정해야 한다.
응답 헤더의Access-Control-Allow-Origin 의 값에 * 문자는 사용할 수 없다.
응답 헤더의Access-Control-Allow-Methods 의 값에 * 문자는 사용할 수 없다.
응답 헤더의Access-Control-Allow-Headers 의 값에 * 문자는 사용할 수 없다. 위와 같은 사항을 준수하면 CORS 정책을 통과하게 된다.
대부분이 이 3가지 시나리오에 의해 작동된다.
문제가 생겼을때 해결법
사실은 대부분 2번이 문제지만 개발간에도 문제가 생기는 경우가 많으므로 두 가지에 대해서 다 이야기해보겠다.
개발 환경에서 문제가 생겼을 경우
- 크롬 확장 프로그램을 사용한다.
- CORS 정책 위반에 의한 차단은 브라우저에서 일어나는 것이므로 애당초 브라우저에서 차단을 풀어주는 것이다.
- 프록시 사이트를 이용한다.
- 모든 출처를 허용한 서버를 이용하여 요청을 하는 방식과 동일하다. 별도로 구축하거나, 시중의 무료 서비스를 사용할 수 있다.
실제 배포 상황에서 문제가 생겼을 경우
- 서버에서 Access-Control-Allow-Origin 헤더 세팅 한다.
- node, nginx, tomcat등 대상이 모두 달라도 헤더 설정은 지원한다.
- 필요한 서버 URL을 포함하여 세팅한다.
- 귀찮다고 *로 설정해버리면 보안 문제가 생긴다 : 해커의 가짜 사이트에서 스크립트가 넘어와서 실행될 수도 있다
번외 : 제대로 된 CORS 정책 정립 하지 않는 경우 및 파생되는 문제점
제대로 된 CORS 정책이 아닌 경우
- Access-Control-Allow-Origin *로 설정하는 경우
- 요청 받은 Origin을 Access-Control-Allow-Origin로 그대로 쓰는 경우
- 사실상 Access-Control-Allow-Origin를 *로 설정하는 것과 다를 바 없다.
- Origin이 null인 경우
- 개발할때 null로 해두고 별 생각없이 Release 해버려 문제가 된다.
파생되는 문제점
아래의 공격에 취약해진다.
- CSRF(Cross Site Request Forgery)
- 사이트 간 요청 위조에 대한 것으로 외부 사이트에 대한 요청을 넣어서 해당 사용자에게 작동시킬 수 있다.
- XSS(Cross Site Scripting)
- 특정 스크립트를 넣어두고 외부에서 스크립트를 가져와 실행하게 할 수 있다.
- Stored XSS의 방식으로 하는 경우가 대부분이다.
- Sensitive Data Exposure
- 사실 이건 위에 두가지 방법으로 일어날 수 있는 문제이다. 민감한 데이터가 노출되는걸 통틀어서 말하는 것이다.