노트

CSRF

Cross-Site Request Forgery

백엔드#security#http · 연결된 개념 10개

쉽게 말하면

CSRF는 내가 로그인해 둔 사이트에 다른 사이트가 내 브라우저를 시켜 몰래 요청을 보내는 공격이에요. 출입증을 목에 건 채 남이 건넨 서류를 들고 들어가면 경비는 내 일인 줄 알고 통과시키는 셈이죠.

비유가 깨지는 곳 공격자는 출입증(쿠키)을 훔치지 않아요. 브라우저가 쿠키를 자동으로 붙이는 점을 이용하죠. 그래서 공격자가 읽을 수 없는 CSRF 토큰을 요청에 같이 싣게 하고 SameSite 쿠키를 함께 써요.

CSRF(Cross-Site Request Forgery, 사이트 간 요청 위조)는 로그인한 사용자가 악성 사이트를 방문했을 때, 그 사이트가 사용자의 브라우저를 시켜 사용자 모르게 원래 사이트에 요청을 보내게 하는 공격이다. 공격자는 토큰을 훔치지 않는다. 브라우저가 쿠키를 알아서 붙여 준다는 점을 이용한다.

왜 가능한가

<!-- evil.example 페이지 안 -->
<form action="https://bank.example/transfer" method="POST">
  <input type="hidden" name="to" value="attacker">
  <input type="hidden" name="amount" value="1000000">
</form>
<script>document.forms[0].submit()</script>
  • 브라우저는 bank.example로 가는 요청에 그 사이트의 쿠키(세션 ID, session identifier)를 자동으로 붙인다. 서버는 정상 로그인 사용자의 요청으로 본다
  • 동일 출처 정책(Same-Origin Policy, SOP)은 다른 출처의 응답을 읽는 것은 막지만, 폼 전송·이미지 요청처럼 보내는 것은 막지 않는다. CSRF는 응답을 읽을 필요가 없다
  • 그래서 쿠키로 인증하는 세션 인증이 주된 대상이다. Authorization 헤더에 토큰을 직접 넣는 방식은 브라우저가 자동으로 붙이지 않아 상대적으로 안전하다(JWT)

방어

  • CSRF 토큰: 서버가 세션별로 추측할 수 없는 값을 발급하고, 상태를 바꾸는 요청에 폼 필드나 헤더로 함께 보내게 한다. 공격자는 이 값을 읽을 수 없어 맞출 수 없다. 서버 저장 방식(synchronizer token)과 쿠키·요청 값을 비교하는 방식(double submit)이 있다(Django의 CSRF 방어)
  • SameSite 쿠키: SameSite=Lax(크롬 계열은 속성이 없는 쿠키도 Lax로 다루지만 모든 브라우저의 기본은 아니다)는 다른 사이트에서 시작한 POST 요청에 쿠키를 싣지 않는다. Strict는 링크 이동에도 안 싣는다. 강력한 기본 방어지만, 하위 도메인이나 오래된 브라우저 같은 예외가 있어 토큰과 함께 쓴다
  • Origin·Referer 검사: 요청 헤더의 출처가 내 사이트인지 확인한다(Origin·Referer 헤더와 Referrer-Policy)
  • GET은 안전하게: 상태를 바꾸는 동작을 GET으로 만들지 않는다. 이미지 태그 하나로도 요청이 나간다

프레임워크 기본값

  • 스프링 시큐리티는 GET·HEAD·OPTIONS·TRACE가 아닌 요청에 CSRF 보호를 기본으로 켠다. 세션 쿠키 없이 토큰만 쓰는 API라면 끄기도 하지만, 근거를 확인하고 끈다(시큐리티 필터체인)
  • Django는 CsrfViewMiddleware가 같은 일을 한다

XSS와의 차이

CSRF는 공격자가 사용자 행세를 하는 것이고, XSS(Cross-Site Scripting)는 내 사이트에서 공격자 스크립트가 돌아 브라우저를 장악하는 것이다. XSS가 있으면 페이지 안의 CSRF 토큰도 읽을 수 있어 CSRF 방어가 무력해진다. CORS(Cross-Origin Resource Sharing)는 읽기를 허용하는 장치이지 CSRF 방어가 아니다.

출처: OWASP CSRF Prevention Cheat Sheet: Synchronizer Token Pattern · Double Submit Cookie · SameSite · Origin 검사

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 브라우저 요청과 서버 간 요청

    같은 HTTP 요청이라도 브라우저에서 보내면 여러 보안 규칙이 강제되고, 서버에서 보내면 아무 제한이 없다. 브라우저 보안 정책은 신뢰할 수 없는 코드로부터 사용자를 지키려고 있기 때문이다.

  • 인증과 인가

    인증(authentication)은 "너는 누구냐"를 확인하는 것이고, 인가(authorization)는 "너는 이걸 해도 되냐"를 판단하는 것이다. 인증이 먼저고 인가가 그다음이다.

  • CSR·SSR·SSG·ISR

    렌더링 전략은 HTML(HyperText Markup Language)을 언제, 어디서 만드느냐의 선택이다. 브라우저에서 만들면 CSR(Client-Side Rendering), 요청마다 서버에서 만들면 SSR(Server-Side Rendering), 빌드 때 미리 만들면 SSG(Static Site Generation), 미리 만든 것을 주기적으로 다시 만들면 ISR(Incremental Static Regeneration)이다.

  • DRF 인증과 권한

    DRF는 요청 처리를 두 단계로 나눈다. 인증 클래스(authentication class)는 요청을 보낸 사용자가 누구인지 식별해 request.user·request.auth를 채우기만 하고, 권한 클래스(permission class)가 그 사용자에게 이 요청을 허용할지 결정한다. 인증만으로는 요청이 막히지 않는다(authn-authz).

  • localStorage와 sessionStorage

    브라우저에 문자열 키-값을 저장하는 Web Storage API의 두 가지. 쿠키와 달리 요청에 자동으로 실리지 않고, 출처 단위로 나뉘며(same-origin-policy), 보통 출처당 5MB 안팎을 쓸 수 있다. 동기 API라 큰 데이터를 자주 읽고 쓰면 메인 스레드를 막는다.

보기 옵션