GN⁺: CSRF 보호와 CORS를 둘다 사용하는 이유는 무엇인가요?
(smagin.fyi)- Cross-Site Request를 고민하다 보니 CSRF 보호와 CORS가 둘 다 필요하다는 것이 처음에는 이해되지 않았음. 하지만 이를 설명하려면 많은 단어가 필요함
CSRF와 CORS
-
CSRF (Cross-Site Request Forgery)
- 과거에는 흔했지만, 현재는 대부분의 웹 프레임워크에서 기본적으로 보호 기능을 제공하여 거의 문제가 되지 않음
- 공격 방식: 사용자가 악성 사이트에서 특정 폼을 클릭하게 만들어 크로스 사이트 요청을 전송하도록 유도함
- 방어 방식: 요청이 타 사이트에서 유입된 것이 아닌지 확인하는 것
-
CORS (Cross-Origin Resource Sharing)
- HTTP 스펙의 일부로, 특정 크로스 사이트 요청을 허용하는 방법을 정의함
- 사전 요청(preflight) 및 응답 헤더를 사용하여 어떤 출처(origin)에서 요청을 보낼 수 있는지 지정함
그렇다면 크로스 사이트 요청이 기본적으로 허용되며 CSRF 보호가 필요한 것인지, 아니면 기본적으로 차단되며 CORS가 필요하여 허용하는 것인지? 정답은 둘 다임.
기본 동작 방식
-
동일 출처 정책(Same-origin policy)
- 브라우저가 강제하는 보안 정책
- 일반적으로 크로스 사이트 쓰기(Write)는 허용, 읽기(Read)는 금지
- 예를 들어, 브라우저는 폼을 통한 POST 요청은 허용하지만, 응답을 읽을 수는 없음
-
SameSite 쿠키 정책
- 2019년에 쿠키의 기본 동작 방식이 변경됨
- 기존에는 크로스 사이트 요청에서도 쿠키가 항상 전송되었음
- 새로운
SameSite
속성이 추가되었으며, 기본값이Lax
로 변경됨 - 2025년 기준, 96%의 브라우저가
SameSite
속성을 지원, 75%가 새로운 기본값(Lax
)을 지원 - 그러나 Safari는 이를 기본값으로 적용하지 않았으며, UCBrowser는 여전히 지원하지 않음
-
사이트(Site)와 출처(Origin)의 차이
-
출처(Origin):
프로토콜 + 호스트명 + 포트
조합 -
사이트(Site):
프로토콜 + 최상위 도메인 + 1
조합 (서브도메인과 포트는 무시됨)
-
출처(Origin):
CORS
- CORS는 동일 출처 정책을 특정 출처(origin)에 대해 예외적으로 허용하는 방식
- 브라우저는 요청을 보내기 전에
OPTIONS
타입의 **사전 요청(preflight request)**을 전송함 - 서버는 응답 헤더를 통해 허용 규칙을 정의 (
Access-Control-*
헤더 사용) - CORS가 적용되는 요청 유형:
-
fetch
및XMLHttpRequest
- 웹 폰트
- WebGL 텍스처
-
canvas
에서drawImage
로 그린 이미지/비디오 프레임 - CSS
shape-outside
속성에서 사용하는 이미지
-
-
단, 폼 제출은 예외적으로 CORS가 적용되지 않음
- HTML 4.0의
<form>
태그는 오래전부터 크로스 사이트 요청을 허용하고 있었음 - 따라서 기존 서버들은 이미 CSRF 공격을 방어하도록 설계되었어야 함
- 서버는 응답을 공유하려면
Access-Control-Allow-Origin
을 설정해야 하지만, 요청 자체는 사전 요청 없이도 수락됨
- HTML 4.0의
질문:
SameSite
정책과 이 방식은 어떻게 일관성을 유지하는가?
CSRF 보호 방법
- 크로스 사이트 쓰기 요청은 허용되지만 응답은 공유되지 않음
- 대부분의 웹사이트에서는 크로스 사이트 쓰기를 허용하고 싶지 않음
-
표준적인 CSRF 방어 방법
- 사용자별 CSRF 토큰을 요청에 포함하여 검증
- 방법:
- 폼 제출: 숨은 입력 필드(hidden input)로 토큰 추가
-
JS 요청: 쿠키 또는
meta
태그에 저장한 후, 요청 헤더나 파라미터에 포함
-
JS 요청은 원래 크로스 사이트가 기본적으로 차단됨
- 하지만 동일 사이트 요청(same-site request)에는 허용됨
- CSRF 토큰을 포함하면 모든 요청에서 동일한 방식으로 검증 가능
-
추가적인 보안 이점
- 브라우저가 기본적으로 응답 읽기를 차단해야 한다는 가정에서 작동
-
Origin
헤더를 검사하는 것보다 더 보안성이 높음
질문: 일부 프레임워크에서는 CSRF 토큰을 주기적으로 변경함. 그 이유는?
브라우저의 역할
- 웹 보안의 핵심은 브라우저가 신뢰할 수 있는지에 달려 있음
- 브라우저는:
- 동일 출처 정책을 강제
- 응답이 허용되지 않으면 읽지 않도록 차단
-
SameSite=Lax
기본값을 적용할지 결정 - CORS를 구현하고, 안전한 사전 요청을 보냄
우리는 사용 중인 브라우저를 신뢰해야 함.
결론
-
SameSite=Lax
가 100% 브라우저에서 지원되면 보안이 더 강화되겠지만,
현재는 여전히 크로스 사이트 POST 요청만 예외적으로 허용되는 상황 - 따라서 개발자는 CSRF 보호를 지속적으로 고려해야 함
"인터넷이 점점 더 안전해지지만, 그만큼 과거와의 호환성도 점점 줄어들고 있음."
출처
Hacker News 의견
-
CORS는 서버가 브라우저에게 어떤 크로스 오리진 요청이 응답을 읽을 수 있는지를 명시적으로 알려주는 메커니즘임
- 기본적으로 브라우저는 크로스 오리진 스크립트가 응답을 읽는 것을 차단함
- 명시적으로 허용되지 않으면 요청 도메인은 응답을 읽을 수 없음
- 예를 들어, evil.com의 스크립트가 bank.com/transactions에 요청을 보내 피해자의 거래 내역을 읽으려 할 수 있음
- 브라우저는 요청이 bank.com에 도달하도록 허용하지만 evil.com이 응답을 읽는 것은 차단함
-
CSRF 보호는 인증된 사용자를 대신하여 악의적인 크로스 오리진 요청이 무단으로 행동을 수행하는 것을 방지함
- 예를 들어, evil.com의 스크립트가 bank.com에서 행동을 수행하도록 요청을 보낼 수 있음 (예: bank.com/transfer?from=victim&to=hacker로 돈을 이체)
- bank.com의 서버 측 CSRF 보호가 이를 거부함 (아마도 요청에 비밀 CSRF 토큰이 포함되어 있지 않기 때문임)
-
CSRF 보호는 쓰기 보호에 관한 것이고, CORS는 읽기 보호에 관한 것임
-
JS로 시작된 요청은 기본적으로 크로스 사이트가 허용되지 않음
- fetch()를 사용하여 허용된 헤더만 사용하면 크로스 사이트 요청을 시작할 수 있음
-
이 주제에 대한 더 나은 설명이 있다고 생각함
- 관련 블로그 링크 제공
-
블로그 게시물의 질문에 대한 응답
- HTML 4.0의 <form> 요소는 어떤 오리진으로도 간단한 요청을 제출할 수 있음
- 이와 관련하여 SameSite 이니셔티브와 어떻게 일치하는지에 대한 질문이 있었음
-
2022년에 MDN CORS 기사에 "간단한 요청" 용어의 출처를 명확히 하기 위해 단락을 추가했음
- 이전에는 fetch 사양에 언급되지 않았다고만 나와 있었음
- 2019년 브라우저의 CSRF 방지 기능이 SameSite=Lax를 지원하거나 기본값으로 설정된 경우에 대한 언급이 없었음
-
SameSite가 CORS 사전 요청과 독립적으로 추가된 것이 혼란스러움
- 브라우저 제작자들이 모든 크로스 오리진 POST 요청에 사전 요청을 요구하지 않은 이유가 궁금함
-
csrf를 사용하지 않아도 안전하다고 생각할 수 있지만, 일부 라이브러리(예: django rest framework)는 콘텐츠 타입 헤더가 설정되면 HTML 폼을 처리할 수 있음
- 이는 사용자의 사이트에 폼을 게시하여 사용자를 대신하여 요청을 보낼 수 있게 함
-
CSRF 토큰이 회전되는 이유에 대한 질문
- OWASP는 이것이 더 안전하다고 하지만 이유를 잘 모르겠음
-
복잡한 주제에 대한 흐름도를 요청함
- 새로운 애플리케이션 플랫폼과 표준 세트를 원함
-
이러한 것들이 쉬운 진단 추적을 지원하지 않음
- 적절히 구성되지 않은 합법적인 사용 사례에 대한 불투명한 오류를 여러 번 경험했음
-
CORS가 등장하기 전에는 페이지 오리진이 아닌 임의의 엔드포인트에 요청을 보낼 수 있었지만 응답을 볼 수 없었던 이유를 이해하지 못함
- 이것이 사양에 우연히 포함된 것인지, XSS를 예상하고 의도적으로 한 것인지, 아니면 주도적인 브라우저가 그렇게 했고 다른 브라우저들이 따라한 것인지 궁금함
-
CSRF 보호에 대한 혼란
- 공격자가 goodsite.com에서 CSRF 토큰을 얻어 badsite.com에 넣고 Alice를 속여 badsite.com에서 goodsite.com으로 요청을 제출하게 하는 것을 막을 방법이 무엇인지 궁금함