Q. 브라우저가 항상 보안 위협을 받는 근본적인 이유는?
➡️ A. 자바스크립트를 구동하기 때문! 자바스크립트로 할 수 있는 것들이 많기 때문이다.
브라우저에서 자바스크립트로 할 수 있는 것들
1. ajax call을 해서 api를 호출할 수 있다
2. 다이나믹하게 DOM을 제어할 수 있다
3. 인증 정보를 브라우저에 저장할 수 있다
4. 인증 정보를 불러올 수도 있다
Q. 어떤 공격이 일어날 수 있을까?
➡️ A. 대표적인 두 가지: XSS, CSRF
클라이언트가 서버를 신뢰하기 때문에 발생하는 이슈
브라우저에서 서버에 메세지 요청시 해킹 관련 코드가 침투된다면? (<script />
코드 )
그에 관한 응답을 받아 브라우저가 렌더하게 된다! ➡️ 해킹 완.
반대로 서버가 클라이언트를 신뢰해서 발생하는 이슈
클라이언트의 도메인과 같은 도메인의 서버로부터 리소스를 받아오려고 할 때는 문제가 없다.
하지만 다른 도메인의 서버로부터 리소스를 받아오고 싶다면?? ➡️ 이때 필요한 것이 CORS 이다!
MDN 뜯어보기❗️
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS (영문)
https://developer.mozilla.org/ko/docs/Web/HTTP/CORS (한글)
⬇️
cross-origin 요청의 예시 : https://domain-a.com
에서 제공되는 프론트 엔드 JavaScript 코드가 XMLHttpRequest를 사용하여 https://domain-b.com/data.json
을 요청하는 경우
보안상의 이유로, 브라우저는 스크립트에서 시작한 cross-origin HTTP 요청을 제한한다. 예를 들어 XMLHttpRequest 및 Fetch API는 same-origin policy를 따른다. 즉, 이 API를 사용하는 웹 애플리케이션은 자신의 출처와 동일한 리소스만 불러올 수 있으며, 다른 출처의 리소스를 불러오려면 그 출처에서 올바른 CORS 헤더를 포함한 응답을 줘야 한다.
CORS 메커니즘은 브라우저와 서버 간의 안전한 cross-origin 요청 및 데이터 전송을 지원한다. 최신 브라우저는 XMLHttpRequest 또는 fetch와 같은 API에서 CORS를 사용하여 cross-origin HTTP 요청의 위험을 완화한다.
GET
이외의 HTTP 메서드)에 대해, CORS specification은 OPTIONS
메서드로 "preflight(사전 전달)"하여 서버에서 지원되는 메서드를 요청하고,이후 항목에서는 시나리오와 함께, 사용한 HTTP 헤더의 상세 내용을 다룬다.
Cross-Origin Resource Sharing이 동작하는 방식을 보여주는 세 가지 시나리오를 제시한다. 모든 예제는 지원하는 브라우저에서 CORS를 생성할 수 있는 XMLHttpRequest를 사용한다.
일부요청은 CORS preflight 를 트리거하지 않는다. "simple requests"는 다음 조건들을 모두 충족하는 요청이다.
예를 들어, https://foo.example
의 웹 컨텐츠가 https://bar.other
도메인의 컨텐츠를 호출하길 원한다. foo.example
에 배포된 자바스크립트에는 아래와 같은 코드가 사용될 수 있다.
const xhr = new XMLHttpRequest();
const url = 'https://bar.other/resources/public-data/';
xhr.open('GET', url);
xhr.onreadystatechange = someHandler;
xhr.send();
이것은 클라이언트와 서버 간에 간단한 통신을 수행하고, CORS 헤더를 사용하여 권한을 처리한다.
이 경우 브라우저가 서버로 전송하는 내용을 살펴보고, 서버의 응답을 확인해보자.
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
요청 헤더의 Origin
을 보면, https://foo.example
로부터 요청이 왔다는 것을 알 수 있다.
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[…XML Data…]
서버는 이에 대한 응답으로 Access-Control-Allow-Origin
헤더를 다시 전송한다.
이와 같이 가장 간단한 접근 제어 프로토콜은 Origin
헤더와 Access-Control-Allow-Origin
을 사용하는 것이다.
이 경우 서버는 Access-Control-Allow-Origin: *
으로 응답해야 하며, 이는 모든 도메인에서 접근할 수 있음을 의미한다. https://bar.other
의 리소스 소유자가 오직 https://foo.example
의 요청만 리소스에 대한 접근을 허용하려는 경우 다음을 전송한다.
Access-Control-Allow-Origin: https://foo.example
이렇게 하면 https://foo.example
이외의 도메인은 cross-site
방식으로 리소스에 접근할 수 없다. 리소스에 대한 접근을 허용하려면, Access-Control-Allow-Origin
헤더에 요청의 Origin
헤더에서 전송된 값이 포함되어야 한다.
"preflighted" request는 위에서 논의한 “simple requests” 와는 달리, 먼저 OPTIONS
메서드를 통해 다른 도메인의 리소스로 HTTP 요청을 보내 실제 요청이 전송하기에 안전한지 확인한다. Cross-site 요청은 유저 데이터에 영향을 줄 수 있기 때문에 이와 같이 미리 전송(preflighted)한다.
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://bar.other/resources/post-here/');
xhr.setRequestHeader('Ping-Other', 'pingpong');
xhr.setRequestHeader('Content-Type', 'application/xml');
xhr.onreadystatechange = handler;
xhr.send('<person><name>Arun</name></person>');
위의 예제는 POST
요청과 함께 함께 보낼 XML body를 만든다. 또한 비표준 HTTP Ping-Other
요청 헤더가 설정된다. 이러한 헤더는 HTTP/1.1의 일부가 아니지만 일반적으로 웹 응용 프로그램에 유용하다. Content-Type application/xml
이고, 사용자 정의 헤더가 설정되었기 때문에 이 요청은 preflighted 처리된다.
(참고: 아래 설명 된 것처럼 실제 POST
요청에는 Access-Control-Request-*
헤더가 포함되지 않는다. OPTIONS 요청에만 필요하다.)
클라이언트와 서버간의 full exchange를 살펴보자. 첫 번째 통신은 preflight request/response이다.
// request
OPTIONS /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST // preflight request의 일부, 실제 요청을 전송할 때 POST 메서드로 전송된다는 것을 알려줌
Access-Control-Request-Headers: X-PINGOTHER, Content-Type // 실제 요청을 전송할 때 X-PINGOTHER, Content-Type 헤더와 함께 전송된다는 것을 서버에 알려줌.
// response
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS // 요청 메서드를 받을 수 있음 = 실제 요청에 메서드를 사용할 수 있음
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type // 요청 헤더를 받을 수 있음 = 실제 요청에 헤더를 사용할 수 있음
Access-Control-Max-Age: 86400 // 또다른 preflight request를 보내지 않고, preflight request에 대한 응답을 캐시할 수 있는 시간
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
### Requests with credentials
preflight request가 완료되면 실제 요청을 전송한다.
// request
POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: https://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: https://foo.example
Pragma: no-cache
Cache-Control: no-cache
<person><name>Arun</name></person>
// response
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
[Some GZIP'd payload]
다른 출처 간 통신에서 좀 더 보안을 강화하고 싶을 때 사용하는 방법이다. request에 opiton값을 주는 것인데, fetch를 할 때 옵션에 credentials: 'include'를 주면 다음 두 가지 조건을 추가함으로써 정책 위반 여부를 확인하는 규칙이 엄격해진다.
쿠키를 설정하는 리소스와 통신할 때,
브라우저는 Access-Control-Allow-Credentials: true
헤더가 없는 응답을 거부한다.
// request
GET /resources/credentialed-content/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example
Cookie: pageAccess=2
// response
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
[text/plain payload]
CORS란, 브라우저가 2개 이상의 서버와 연결되는 것을 예외적으로 허용하는 정책
'공인된 서버'에서 CORS를 허용하면, 서버 2개까지 연결 가능하다.
(CORS 허용은 서버에서 하는 것이다!!)
preflight request에서 options 메소드의 역할
preflight request는 simple request보다 보안이 좋다 (악성코드를 사전에 체크 가능)
같은 서버라고 판단하는 3가지 기준: 프로토콜,도메인,포트번호가 같으면 같은 서버 (외우자)
클라이언트는 서버가 어떤 origin의 요청을 허용하는지 알 수 없다. Access-Control-Allow-Origin
의 value 정보는 서버만 알고 있고, 클라이언트는 origin이 허용되었는지 아닌지만 알 수 있다.
모든 cross origin 요청이 preflight request를 발생시키는 것은 아니다.
https://nodejs.org/ko/docs/guides/anatomy-of-an-http-transaction/