31P by neo 12일전 | ★ favorite | 댓글 1개
  • 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 조합 (서브도메인과 포트는 무시됨)

CORS

  • CORS는 동일 출처 정책을 특정 출처(origin)에 대해 예외적으로 허용하는 방식
  • 브라우저는 요청을 보내기 전에 OPTIONS 타입의 **사전 요청(preflight request)**을 전송함
  • 서버는 응답 헤더를 통해 허용 규칙을 정의 (Access-Control-* 헤더 사용)
  • CORS가 적용되는 요청 유형:
    • fetchXMLHttpRequest
    • 웹 폰트
    • WebGL 텍스처
    • canvas에서 drawImage로 그린 이미지/비디오 프레임
    • CSS shape-outside 속성에서 사용하는 이미지
  • 단, 폼 제출은 예외적으로 CORS가 적용되지 않음
    • HTML 4.0의 <form> 태그는 오래전부터 크로스 사이트 요청을 허용하고 있었음
    • 따라서 기존 서버들은 이미 CSRF 공격을 방어하도록 설계되었어야 함
    • 서버는 응답을 공유하려면 Access-Control-Allow-Origin을 설정해야 하지만, 요청 자체는 사전 요청 없이도 수락됨

질문: SameSite 정책과 이 방식은 어떻게 일관성을 유지하는가?

CSRF 보호 방법

  • 크로스 사이트 쓰기 요청은 허용되지만 응답은 공유되지 않음
    • 대부분의 웹사이트에서는 크로스 사이트 쓰기를 허용하고 싶지 않음
  • 표준적인 CSRF 방어 방법
    • 사용자별 CSRF 토큰을 요청에 포함하여 검증
    • 방법:
      • 폼 제출: 숨은 입력 필드(hidden input)로 토큰 추가
      • JS 요청: 쿠키 또는 meta 태그에 저장한 후, 요청 헤더나 파라미터에 포함
  • JS 요청은 원래 크로스 사이트가 기본적으로 차단됨
    • 하지만 동일 사이트 요청(same-site request)에는 허용됨
    • CSRF 토큰을 포함하면 모든 요청에서 동일한 방식으로 검증 가능
  • 추가적인 보안 이점
    • 브라우저가 기본적으로 응답 읽기를 차단해야 한다는 가정에서 작동
    • Origin 헤더를 검사하는 것보다 더 보안성이 높음

질문: 일부 프레임워크에서는 CSRF 토큰을 주기적으로 변경함. 그 이유는?

브라우저의 역할

  • 웹 보안의 핵심은 브라우저가 신뢰할 수 있는지에 달려 있음
  • 브라우저는:
    • 동일 출처 정책을 강제
    • 응답이 허용되지 않으면 읽지 않도록 차단
    • SameSite=Lax 기본값을 적용할지 결정
    • CORS를 구현하고, 안전한 사전 요청을 보냄

우리는 사용 중인 브라우저를 신뢰해야 함.

결론

  • SameSite=Lax가 100% 브라우저에서 지원되면 보안이 더 강화되겠지만,
    현재는 여전히 크로스 사이트 POST 요청만 예외적으로 허용되는 상황
  • 따라서 개발자는 CSRF 보호를 지속적으로 고려해야 함

"인터넷이 점점 더 안전해지지만, 그만큼 과거와의 호환성도 점점 줄어들고 있음."

출처

  1. Same-origin policy
  2. caniuse SameSite cookie attribute
  3. OWASP CSRF cheatsheet
  4. CORS wiki with requirements
  5. CORS spec
  6. CORS on MDN
  7. Preflight request
  8. Origin request header
  9. Origin and Site
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으로 요청을 제출하게 하는 것을 막을 방법이 무엇인지 궁금함