우선 CORS를 말하기 전에 SOP에 대해 먼저 알 필요성이 있습니다. SOP는 Same-Origin Policy로 말 그대로 동일 출처 정책인데, 동일한 출처 사이에서만 리소스를 공유할 수 있다는 보안 규칙입니다. 동일 출처의 기준은 스킴(scheme), 도메인, 포트 이 3가지를 기준으로 합니다. 오래 전에는 대부분의 처리가 같은 도메인 내에서 일어났고, 오히려 그 당시에는 다른 출처로 요청을 보내는 것을 악의적인 행위(CSRF, XSS 등)로 간주되기도 했습니다. 따라서 SOP는 보안을 위한 정책이었고 브라우저 차원에서 막는 것이 당연한 일이었습니다. 하지만 웹 생태계가 다양해지고 외부 API를 사용하거나 클라이언트와 서버를 분리해서 개발하는 경우도 많아지게 되면서 여러 서비스들간에 데이터를 주고 받을 필요성이 점점 더 생기게 됩니다. 그래서 별도의 기준이 충족되면 리소스 공유가 되도록 생겨난 개념이 CORS입니다. CORS는 Cross-Origin Resource Sharing의 약자인데, 다른 출처간에 리소스를 공유할 수 있도록 하는 체제 같은 것을 말합니다. Postman으로 API 테스트해보거나 스프링 같은 백엔드에서는 문제없이 데이터를 받아오는데 브라우저, 즉 프론트에서 CORS 에러를 볼 수 있는 이유는 브라우저 차원에서 이러한 이유로 막고 있기 때문입니다.
브라우저는 다른 출처끼리의 요청이 보내질 때는 요청에 origin이라는 header를 추가합니다. 이 header에는 요청하는 쪽의 스킴, 도메인, 포트가 담깁니다. 여기서 스킴은 요청할 자원에 접근할 방법, 즉 http, ftp, telnet 등을 말합니다. 그래서 이 요청을 받은 서버는 응답을 보낼 header에 지정된 Access-Control-Allow-Origin 정보를 실어서 보냅니다. 결론적으론 최초에 요청했던 header에 담겼던 스킴, 도메인, 포트 정보가 Access-Control-Allow-Origin 정보에 담겨 있으면 안전한 요청으로 간주하고 응답 데이터를 받아오게 됩니다.
추가로 토큰 등 사용자 식별 정보가 담긴 요청에 대해서는 좀 더 엄격합니다. 일단 보내는 쪽에서는 보내는 옵션에 credentials 항목을 true로 세팅해야 하고, 받는 쪽에서도 단순히 모든 출처에 허용한다는 와일드카드 대신에 정확하게 명시해야 하며, Access-Control-Allow-Credentials 항목을 true로 맞춰줘야 합니다.
CORS와 관련된 요청은 Simple Request와 Preflight Request가 있는데, GET이나 POST 등의 Simple Request는 예비 요청 없이 본 요청만 보내는 것이고, PUT이나 DELETE 등의 Preflight Request는 브라우저가 본 요청을 보내기 전에 보내는 예비 요청을 보내서 안전한지 확인하는 과정입니다. 그 외에 별도로 보안을 좀 더 강화한 Credentialed Request 또한 있습니다.
꼬리 질문
CORS가 허용되는 조건이 무엇인가요?
요청을 받는 백엔드쪽에서 이걸 허락할 다른 출처들을 명시해두면 됩니다. CORS 옵션을 넣어서 허용할 사이트들을 명시적으로 적어주면 CORS가 가능해집니다.
Preflight는 무엇인가요?
Preflight는 실제 요청이 CORS를 위반하지 않았는지를 미리 확인하고, 부작용으로부터 서버를 보호하기 위해 전송한다.
스킴과 프로토콜은 혼용해서 많이 사용하는데, 차이에 대해서 설명해주실 수 있으실까요?
프로토콜은 컴퓨터나 장치들이 서로 정보를 주고받기 위한 통신 규약이며, 스킴은 URI의 구성 요소로써, 어떤 프로토콜을 사용할지를 나타낼 뿐이다.
CSRF와 XSS를 언급하셨는데 간략하게 설명해주실 수 있으실까요?
CSRF는 Cross Site Request Forgery의 약자로 사이트간의 요청 위조, 즉 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위(데이터 수정, 삭제 등)을 특정 웹사이트에 요청하게 하는 공격이라고 볼 수 있습니다. 우리가 흔히 접하는 피싱도 CSRF의 일종이라고 볼 수 있습니다. XSS는 인증된 세션 없이도 공격을 진행할 수 있다는 차이점이 있는데, 쉽게 말해서 XSS는 공격대상이 클라이언트고 CSRF는 서버라고 볼 수 있습니다. XSS는 사용자가 입력 가능한 input이나 textarea 같은 것을 통해 별도의 악성 스크립트가 담긴 글을 올리고 실행시키는 방법입니다. 이를 통해 유저의 쿠키 정보를 탈취, 유저의 비밀번호를 변경하는 api를 호출하거나 하는데, 실제로 특정 글을 클릭했을 때 회원이 탈퇴되어버리는 사건이 있기도 했다고 알고 있습니다.
a 태그에 href target attribute에 blank를 줌으로서 외부 링크가 새 창으로 열리게 되는데, 이에 따른 보안 대응은 어떻게 하는 편이 좋을까요?
target="_blank"를 rel="noreferrer"와 rel="noopener" 없이 사용하면 웹사이트가 window.opener API 악용 공격에 취약해집니다. 다만, 최근 브라우저(Firefox 79+ 등)는 target="_blank"를 지정하면 임의로 rel="noopener" 설정과 동일한 보호 수준을 적용합니다.