본문으로 건너뛰기
개발 머꼬
개발 노트보안
hohyeon.dev28

쿠키 기반 인증에 SameSite만 믿었다가 CSRF 방어가 뚫릴 뻔한 이유

  • #Auth
  • #Engineering Note
  • #Security

문제 발생

쿠키에 SameSite=Lax를 설정해뒀으니 CSRF(Cross-Site Request Forgery) 방어가 충분하다고 판단했는데, 보안 점검에서 특정 상황의 CSRF 공격이 여전히 가능하다는 지적을 받았습니다.

원인 분석

SameSite 쿠키 속성은 확실히 CSRF 방어에 큰 도움이 됩니다 — 다른 사이트에서 발생한 요청에 쿠키를 자동으로 실어 보내지 않도록 브라우저 차원에서 막아주기 때문입니다. 하지만 완전한 방어는 아닙니다.

  1. SameSite=Lax(대부분 브라우저의 기본값)는 최상위 레벨 GET 네비게이션(링크 클릭, 주소창 이동)에는 쿠키를 여전히 포함시킵니다 — 폼의 <form method="GET">이나 특정 조건의 요청은 이 예외에 걸릴 수 있습니다. 상태를 변경하는 작업이 GET으로 처리되도록 설계되어 있다면(원칙적으로 지양해야 하지만) 이 예외가 공격 표면이 될 수 있습니다.
  2. 오래된 브라우저나 특정 환경은 SameSite를 아예 지원하지 않거나 다르게 해석할 수 있습니다 — 방어를 전적으로 클라이언트(브라우저) 동작에만 의존하면, 그 클라이언트가 예상과 다르게 동작하는 경우 방어가 통째로 사라집니다.
  3. 서브도메인 간 쿠키 공유가 필요해서 SameSite=None을 쓰는 경우(이 프로젝트의 크로스 서브도메인 인증과는 다른 상황이지만, 그런 아키텍처에서는), SameSite가 주는 보호 자체가 없어집니다.

해결 방안

  1. SameSite 쿠키와 CSRF 토큰을 함께 씁니다 — 서로 다른 계층에서 방어하는 상호 보완적인 수단으로 취급합니다. SameSite가 대부분의 공격을 브라우저 차원에서 막아주고, CSRF 토큰이 그 예외 상황과 SameSite를 지원하지 않는 환경까지 커버합니다.
  2. CSRF 토큰의 기본 원리는 간단합니다 — 서버가 폼/세션마다 예측 불가능한 토큰을 발급해 폼에 심어두고, 그 폼을 제출할 때 토큰을 함께 제출받아 검증합니다. 공격자는 피해자의 브라우저에 있는 이 토큰 값을 알 수 없으므로(같은 출처 정책으로 보호됨), 위조된 요청에는 올바른 토큰을 넣을 수 없습니다.
  3. 상태를 변경하는 작업은 항상 GET이 아니라 POST/PUT/DELETE 등으로 만듭니다 — 이건 CSRF 방어 이전에 HTTP 시맨틱상으로도 맞는 설계이고, SameSite=Lax의 예외(최상위 GET 네비게이션) 범위 자체에서 벗어나게 해줍니다.
  4. 프레임워크가 제공하는 공식 CSRF 방어 메커니즘을 우선 사용합니다(예: Spring Security의 CSRF 토큰 지원) — 자체 구현은 토큰 생성의 예측 가능성, 저장 위치, 만료 처리 등에서 미묘한 취약점이 생기기 쉽습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.