이미 보신 것처럼 CORS는 원산지 간 자원 공유의 약어입니다. 하지만 실제로 어떤 의미일까요? CORS란 무엇입니까? 자, 만약 우리가 위키백과의 정의에 따르면, "CORS는 웹페이지의 제한된 자원들이 첫번째 자원이 제공된 도메인 외부의 다른 도메인으로부터 요청될 수 있게 하는 메커니즘이다." 라고 한다면, 여러분이 그 문장을 읽기 전보다 더 혼란스러웠다면 용서받을 것입니다.
CORS를 정의하기 전에 먼저 무엇이 기본 동작을 정의하는지 확인하는 것이 가장 좋습니다. 이 동작은 여전히 기본 동작을 정의하며, 이것이 바로 여러분이 지금 이 내용을 읽고 있는 이유일 것입니다. 이러한 CORS의 전조는 "동일 원산지" 정책이라고 불렸습니다. 간단히 말해, 브라우저가 특정(하위) 도메인에서 스크립트(예: 버튼 핸들러 또는 일부 비동기 위젯)를 로드할 때 스크립트가 원래 (하위) 도메인으로만 요청을 할 수 있음을 나타냅니다.
Cross-Origin 리소스 공유
그렇다면, CORS는 무엇일까요? 간단히 말해, CORS는 이 정책의 동작을 변경할 수 있는 기능을 제공하는 메커니즘으로, www.example.com에서 정적 콘텐츠를 호스팅하고 api.example.com에서 백엔드 API를 호스팅할 수 있습니다. 한 하위 도메인의 리소스가 다른 하위 도메인의 리소스를 요청하기 때문에 이러한 요청을 교차 오리진 요청이라고 합니다.
이 모든 것은 HTTP 요청 헤더 집합과 "CORS 헤더"라고 하는 해당 응답 헤더 집합을 교환하는 프리플라이트 요청을 통해 제어되며, 각 헤더는 적용되는 제한을 완화하기 위해 Same-Origin 정책의 다른 요소를 수정합니다.
일반적으로 와일드카드 "*" 응답 헤더를 잔인하게 설정하는 변형된 와일드카드 "*" 응답 헤더를 포함하는 경우, 이 설정 방법에 대한 끔찍한 조언이 많이 있습니다(특히 인기 있는 포럼). 이 기사에서는 교차 오리진 리소스 공유에 대한 일반적인 오해 중 일부를 제거하고 올바르게 작동하는 방법에 대한 유용한 조언을 제공하려고 합니다.
CORS 어떻게 동작하나
위에서 언급한 바와 같이, 한 오리진에서 로드된 스크립트가 다른 오리진(따라서 교차 오리진 리소스 공유라는 이름)에 대한 요청을 시도할 때 CORS 워크플로가 시작됩니다.
이 워크플로는 브라우저가 외부 웹 서버에 대한 사전 비행 요청을 자동으로 작성하는 것으로 시작됩니다. 이 프리플라이트 요청은 HTTP 메서드 OPTIONS를 사용하며 나중에 자세히 설명하기로 하는 여러 HTTP 헤더가 있습니다. 그런 다음 외부 웹 서버는 이러한 프리플라이트 요청 헤더의 유효성을 검사하여 해당 오리진의 스크립트가 지정된 요청 메서드와 프리플라이트 요청 헤더에 지정된 사용자 지정 요청 헤더를 사용하여 리소스에 실제 요청을 할 수 있도록 해야 합니다.
확인되면 외부 웹 서버는 자체 HTTP 헤더 집합을 사용하여 응답해야 합니다. 이러한 응답 헤더는 허용되는 오리진, 요청 방법, 사용자 지정 헤더의 범위, 자격 증명(예: 쿠키, 인증 헤더)을 보낼 수 있는지 여부, 브라우저가 응답을 보관하는 기간을 정의합니다. 이렇게 하면 브라우저는 스크립트가 수행하고자 하는 향후 요청에 대한 사전 유효성 확인의 한 형태로 해당 응답을 캐시된 상태로 유지할 수 있습니다.
그러면 요청 헤더란 무엇이며, 어떤 역할을 할까요?
액세스 제어 요청 헤더는 매우 간단하며 대부분의 경우 스스로 설명할 수 있습니다.
"Origin" – 요청을 하는 스크립트가 브라우저에 제공된 (하위) 도메인입니다.
"Access-Control-Request-Method" – 스크립트가 실제 요청에 사용할 메서드입니다.
"Access-Control-Request-Headers" – 브라우저가 실제 요청과 함께 보낼 것으로 예상하는 사용자 지정 헤더입니다.
At a Bare Minimum
"Origin"을 서버의 액세스 목록과 비교하여 해당 원본의 스크립트가 허용 가능한지 확인해야 합니다. 이렇게 해도 공격이 100% 중단되지는 않지만 최소한 공격자의 속도를 늦추고 공격자의 의욕을 꺾을 수 있으며 자동화된 악성 광고 공격이 성공할 위험을 크게 줄일 수 있습니다. 만약 "Origin" 헤더가 당신의 접근 목록과 일치하지 않는다면, 공격이 올지도 모르는 좋은 카나리아입니다.
검증하기 쉬운 또 다른 점은 'Access-Control-Request-Method'가 실제로 API에서 사용되는 지원되는 HTTP 메서드라는 것입니다. "Origin" 헤더와 마찬가지로 API가 지원하지 않는 헤더가 있으면 어떤 일이 일어나고 있다는 것을 알 수 있습니다.
Helpful Canaries
이러한 두 헤더 모두에 대해 유효성 검사 실패 경고를 설정하면 공격이 들어오고 있다는 신뢰할 수 있는 조기 경고를 제공하여 사용자를 신속하게 보호할 수 있습니다.
안타깝게도 'Access-Control-Request-Headers' 요청 헤더는 브라우저가 인식한 것보다 사용자 지정 헤더가 일부, 없거나 더 많을 수 있기 때문에 그다지 신뢰할 수 없습니다. 따라서 다른 모든 항목이 유효한 경우 무시해도 상당히 안전하지만 다른 항목 중 하나가 실패할 경우 이를 기록해야 할 수 있습니다. 공격자로부터 발생한 향후 요청에 유용한 시그니처가 될 수 있기 때문입니다.
Access Control Allow Headers and How to Respond to a CORS Request
액세스 제어 허용 헤더는 요청 헤더보다 조금 더 복잡하며, 이는 대부분 대부분의 브라우저에서 표준이 제대로 구현되지 않았기 때문입니다. 하지만 문제를 자세히 살펴보고 문제를 피하는 방법에 대해 알아보기 전에 먼저 헤더가 무엇인지, 그리고 헤더가 수행하는 작업에 대해 알아보겠습니다.
"Access-Control-Allow-Origin" – "Origin" 요청 헤더가 액세스 목록과 일치할 경우 이 헤더는 해당 요청 헤더의 내용을 반영해야 합니다.
Access-Control-Allow-Credentials – 이 헤더는 쿠키 또는 Authorization 헤더와 같은 인증 자격 증명을 보내는 데 이 오리진의 코드가 허용되는지 여부를 브라우저에 표시하는 부울입니다.
Access-Control-Expose-Headers – 이것은 쉼표로 구분된 목록으로, 서버의 응답에서 실제 요청에 대한 헤더를 요청을 수행하는 스크립트에 표시해야 합니다.
Access-Control-Max-Age – 최대 기간 헤더는 브라우저가 향후 교차 출발지 요청의 오버헤드를 줄이기 위해 이 교차 출발지 요청의 비행 전 검사에 대한 응답을 캐시에 보관해야 하는 기간을 나타냅니다.
Access-Control-Allow-Methods – 이 머리글은 오리진 머리글에 명시된 (하위) 도메인에서 오는 스크립트가 수행할 수 있는 모든 방법을 나열합니다.
Access-Control-Allow-Headers – 프리플라이트 요청에 'Access-Control-Request-Header'가 포함된 경우 이 헤더는 해당 내용을 브라우저에 반영하거나 와일드카드로 응답해야 합니다.
주의사항
`Access-control-allow-origin`
언뜻 보기에 'Access-Control-Allow-Origin' 헤더는 와일드카드 하위 도메인이나 지원하려는 모든 도메인 및 하위 도메인과 일치하도록 정적으로 설정할 수 있는 쉼표로 구분된 목록과 같은 것을 지원하는 것처럼 보일 수 있습니다. 이렇게 하면 브라우저가 기본 설정을 수행해야 하는 횟수를 크게 줄일 수 있습니다.세션의 ts입니다. 그러나 유감스럽게도 그렇지 않으며, 그렇게 하면 정의되지 않은 동작이 발생합니다. 이것은 스택 오버플로 같은 포럼에 대한 대부분의 나쁜 조언의 근원이 되는 경향이 있습니다. 왜냐하면 이 표준이 브라우저에서 단순히 이 검사를 모두 비활성화하는 일반적인 와일드카드를 지원하는 것처럼 이상하게 보일 수 있기 때문입니다. 그래서 사람들은 이것을 쉬운 해결책으로 여기죠. 결과적으로 사용자 데이터가 위험에 노출됩니다.
와일드카드가 뭐가 그리 나쁜가요?
앞에서 설명한 것처럼 'Access-control-allow-origin'을 '*'로 설정하면 동일한 오리진 정책이 사실상 비활성화됩니다. 즉, 브라우저는 로드되는 모든 스크립트에서 교차 오리진 리소스에 대한 거의 모든 요청을 허용합니다. 이건 그렇게 나쁘지 않은 것 같습니다. 왜냐하면 당신은 당신의 사이트에 있는 모든 코드를 신뢰하기 때문입니다. 그렇죠? 그러나 브라우저가 현재 오리진을 필터링하지 않기 때문에 모든 사이트(악성 피싱 사이트 포함)의 모든 코드가 실제로 해당 리소스에 요청을 할 수 있습니다.
최신 브라우저가 최소한 보안을 의식한다는 점을 고려할 때, 널리 사용되는 포럼에서 인증 HTTP 헤더나 쿠키와 같은 자격 증명을 사용해야 하는 와일드카드 복사-파스타를 따르려고 하면 교차 오리진 요청이 실패합니다. 이는 이 취약성을 최소한 부분적으로만 수정하기 위해 브라우저에서 'access-control-allow-origins'가 와일드카드로 설정된 경우 'access-control-allow-credentials' 헤더를 설정할 수 없기 때문입니다. 이는 궁극적으로 인증 토큰을 사용자 지정 헤더로 보내 사용자 데이터를 악성 코드에 완전히 노출시키는 잘못된 아이디어로 이어집니다.
Can't I Just Reflect the Origin?
이 솔루션은 겉으로는 스마트해 보이지만, 오리진 필드의 유효성 검사 없이, 이 블라인드 반사는 브라우저의 'access-control-allow-credentials' 헤더와 와일드카드 'access-control-allow-origins'를 모두 설정하는 차단 기능을 완전히 우회하기 때문에 실제로 와일드카드보다 훨씬 더 나쁩니다.w)는 브라우저의 관점에서 와일드카드가 아닙니다. 잠재적으로 노출된 데이터 목록에 인증 자격 증명을 추가합니다.
공격의 주요한 역할
공격의 위험과 컨텍스트는 잘못된 구성의 특성과 요청을 인증하는 방법에 따라 달라집니다. 최신 브라우저는 가장 심각한 구성 오류(예: 와일드카드 정책 사용)의 영향을 완화하기 위해 최선을 다합니다. 그러나 여전히 통과해야 하는 등가물도 있습니다.
위에서 정의한 최악의 시나리오, 즉 블라인드 리플렉션(blind reflection)을 가장 쉽게 활용할 수 있는 시나리오로 생각해 보겠습니다.
블라인드 리플렉션을 이용하려면 공격 대상자(대상 클라이언트 중 하나)가 악의적인 코드를 제어하거나 주입할 수 있는 사이트를 탐색하기만 하면 됩니다. 이 사이트로부터 코드는 요청을 수행하는 데 필요한 인증 쿠키를 브라우저에서 공격 대상자로 요청할 수 있습니다.
성공한 공격 프로파일의 다이어그램입니다.
이 예에서 무슨 일이 일어났을까요? 첫째, 공격 대상자는 악의적인 사이트로 이동합니다(피싱 전자 메일 또는 악의적인 광고가 있는 웹 사이트만 검색했을 수도 있음). 페이지가 로드되면 브라우저는 페이지에서 Javascript를 실행합니다. 그런 다음 이 "evil.js"는 브라우저(대상으로부터 CORS 헤더의 유효성을 검사한)가 해당 리소스에 대한 공격 대상자의 쿠키를 의무적으로 첨부하는 취약한 대상에 요청을 합니다. 그런 다음 대상이 응답하면 "evil.js"는 이 응답을 악의적인 웹 사이트로 전달합니다.
공격은 여기서 멈출 필요가 없으며 일부 C&C 코드를 evil.js에 포함하면 악의적인 사이트를 대신하여 취약한 사이트로 모든 명령을 릴레이하기 시작할 수 있습니다.
다른 사람들이 2016년까지 보여주었듯이, 이것은 꽤 흔한 취약점이었고, 일부 정규식에서는 단 한 번의 실수만 범합니다. 불행하게도, 이것은 겉보기에는 여전히 사실입니다.
CORS를 활성화하기 위한 모범 사례는 무엇입니까?
스크립트가 교차 오리진 리소스에 대해 유효한 요청을 할 것으로 예상되는 오리진에서 생성되었는지, 그리고 브라우저가 로드하는 모든 스크립트가 해당 리소스에 연결하는 것만 허용하지 않는지 확인합니다. 적절한 액세스 목록에 대해 모든 액세스 제어 요청 헤더를 확인해야 합니다. 이 구현은 특히 오리진 헤더에서 약간 까다로울 수 있으며 웹은 구문 분석 방법의 사소한 잘못된 구성과 모든 종류의 사이트를 공격자의 조작에 노출시키는 잘못된 정규식으로 가득 차 있습니다. 그러나 단순하게 유지하면 신뢰할 수 있는 값의 배열과 안전한 문자열 비교를 통해 안전하게 수행할 수 있습니다. 만약 당신의 리스트와 완벽하게 일치하지 않는다면 403 Fundbidden으로 응답하세요.
둘 이상의 "Origin"을 지원하려면 브라우저가 "access-control-allow-origin" 헤더의 오리진 목록을 지원할 때까지 유효한 "Origin" 요청 헤더를 "access-control-allow-origin" 헤더에 반영하는 것 외에는 선택의 여지가 없습니다.`