티스토리 뷰
]로그인 기능을 만들다 보면 쿠키, 세션, JWT, CORS 같은 개념을 차례대로 만나게 됩니다.
그중에서도 쿠키 기반 인증을 사용할 때 반드시 함께 이해해야 하는 보안 개념이 있습니다.
바로 CSRF입니다.
CSRF는 처음 보면 이름부터 어렵습니다.
Cross-Site Request Forgery
한국어로는 보통 사이트 간 요청 위조라고 부릅니다.
쉽게 말하면, 사용자가 어떤 사이트에 로그인한 상태를 악용해서 사용자가 의도하지 않은 요청을 보내게 만드는 공격입니다.
예를 들어 사용자가 은행 사이트에 로그인한 상태에서 악성 사이트를 방문했다고 해보겠습니다.
악성 사이트가 사용자의 브라우저를 이용해 은행 사이트로 송금 요청을 보내게 만들 수 있다면 매우 위험합니다.
사용자는 직접 송금 버튼을 누르지 않았지만, 브라우저는 이미 은행 사이트의 로그인 쿠키를 가지고 있기 때문에 요청에 쿠키가 함께 전송될 수 있습니다.
이번 글에서는 CSRF가 무엇인지, 왜 쿠키 기반 인증에서 특히 문제가 되는지, CORS와는 무엇이 다른지, Django에서는 어떻게 방어하는지 정리해보겠습니다.
CSRF란?
CSRF는 Cross-Site Request Forgery의 약자입니다.
공격자가 사용자의 브라우저를 속여, 사용자가 로그인한 다른 사이트로 원치 않는 요청을 보내게 만드는 공격입니다.
핵심은 다음입니다.
사용자는 정상 사이트에 로그인되어 있음
→ 브라우저에는 로그인 쿠키가 있음
→ 악성 사이트가 정상 사이트로 요청을 보내게 유도
→ 브라우저가 쿠키를 자동으로 함께 보냄
→ 서버가 정상 사용자의 요청으로 오해할 수 있음
즉, 공격자는 사용자의 비밀번호나 쿠키 값을 직접 알 필요가 없습니다.
브라우저가 이미 로그인 쿠키를 가지고 있고, 요청을 보낼 때 쿠키를 자동으로 포함한다는 점을 악용합니다.
CSRF 공격 예시
예를 들어 사용자가 다음 사이트에 로그인해 있다고 해보겠습니다.
https://bank.example.com
은행 사이트는 쿠키 기반 세션 인증을 사용합니다.
사용자가 로그인하면 브라우저에 다음과 같은 세션 쿠키가 저장됩니다.
Set-Cookie: sessionid=abc123; HttpOnly; Secure
이제 사용자가 다른 탭에서 악성 사이트에 접속합니다.
https://evil.example.com
악성 사이트가 다음과 같은 HTML을 포함하고 있다고 가정해보겠습니다.
<form action="https://bank.example.com/transfer" method="POST">
<input type="hidden" name="to" value="attacker">
<input type="hidden" name="amount" value="100000">
</form>
<script>
document.forms[0].submit();
</script>
사용자가 이 페이지에 접속하면 브라우저는 자동으로 은행 사이트에 POST 요청을 보낼 수 있습니다.
문제는 이 요청이 bank.example.com으로 가기 때문에, 브라우저가 은행 사이트 쿠키를 함께 보낼 수 있다는 점입니다.
POST /transfer
Host: bank.example.com
Cookie: sessionid=abc123
은행 서버가 단순히 세션 쿠키만 보고 요청을 신뢰한다면, 사용자가 직접 보낸 요청처럼 처리할 수 있습니다.
이것이 CSRF 공격의 핵심입니다.
CSRF가 가능한 이유
CSRF는 브라우저와 쿠키의 기본 동작 때문에 발생할 수 있습니다.
브라우저는 특정 도메인에 대한 요청을 보낼 때, 그 도메인에 해당하는 쿠키를 자동으로 포함할 수 있습니다.
사용자가 직접 버튼을 눌렀는지, 악성 사이트의 코드가 요청을 유도했는지 서버는 쿠키만 봐서는 구분하기 어렵습니다.
사용자가 직접 요청
→ Cookie: sessionid=abc123
악성 사이트가 유도한 요청
→ Cookie: sessionid=abc123
서버 입장에서는 둘 다 같은 로그인 사용자의 요청처럼 보일 수 있습니다.
그래서 쿠키 기반 인증에서는 “요청에 쿠키가 있다”는 것만으로 충분하지 않습니다.
이 요청이 정말 우리 사이트의 정상적인 화면에서 사용자의 의도로 발생한 요청인지 확인하는 장치가 필요합니다.
CSRF와 쿠키 기반 인증
CSRF는 특히 쿠키 기반 인증에서 중요합니다.
쿠키는 브라우저가 자동으로 요청에 포함하기 때문입니다.
예를 들어 세션 기반 로그인은 보통 다음 흐름으로 동작합니다.
로그인 성공
→ 서버가 sessionid 쿠키 발급
→ 브라우저가 쿠키 저장
→ 이후 요청마다 쿠키 자동 전송
이 자동 전송이 편리함을 제공합니다.
사용자는 매번 토큰을 직접 붙일 필요가 없고, 브라우저가 알아서 세션 쿠키를 보내줍니다.
하지만 바로 이 특성이 CSRF 공격의 기반이 됩니다.
장점
→ 쿠키가 자동으로 전송되어 로그인 상태 유지가 편리함
위험
→ 원치 않는 cross-site 요청에도 쿠키가 함께 전송될 수 있음
따라서 쿠키 기반 인증을 사용할 때는 CSRF 방어를 함께 설계해야 합니다.
CSRF와 XSS의 차이
CSRF와 함께 자주 나오는 개념이 XSS입니다.
둘 다 웹 보안 공격이지만 성격이 다릅니다.
CSRF
CSRF는 사용자가 이미 로그인한 상태를 이용해, 사용자가 의도하지 않은 요청을 보내게 만드는 공격입니다.
공격자가 응답 내용을 읽는 것이 핵심은 아닙니다.
핵심은 서버에 상태 변경 요청을 보내게 만드는 것입니다.
예를 들어 다음이 공격 대상이 될 수 있습니다.
- 송금
- 이메일 변경
- 비밀번호 변경
- 게시글 삭제
- 주문 취소
- 관리자 설정 변경
XSS
XSS는 공격자가 웹 페이지에 악성 스크립트를 실행하게 만드는 공격입니다.
만약 XSS가 가능하면 공격자는 사용자의 페이지 안에서 JavaScript를 실행할 수 있습니다.
이 경우 쿠키 탈취, 화면 조작, 사용자 대신 API 호출 같은 더 넓은 공격이 가능해질 수 있습니다.
정리하면 다음과 같습니다.
CSRF
→ 사용자의 브라우저를 이용해 원치 않는 요청을 보내게 함
XSS
→ 페이지 안에서 악성 JavaScript를 실행하게 함
둘은 다르지만 함께 고려해야 합니다.
특히 XSS가 있으면 CSRF 토큰도 탈취되거나 우회될 수 있으므로, XSS 방어도 매우 중요합니다.
CSRF와 CORS는 다르다
앞에서 CORS를 다뤘습니다.
CORS와 CSRF는 모두 cross-site 상황과 관련이 있어서 헷갈리기 쉽습니다.
하지만 둘은 해결하는 문제가 다릅니다.
CORS
CORS는 브라우저가 다른 origin의 응답을 JavaScript 코드에 전달해도 되는지 판단하는 정책입니다.
CORS
→ 브라우저가 cross-origin 응답을 읽도록 허용할 것인가?
CSRF
CSRF는 사용자가 의도하지 않은 요청이 서버에 전달되어 처리되는 문제입니다.
CSRF
→ 사용자가 의도하지 않은 상태 변경 요청을 막을 것인가?
중요한 점은 CORS가 CSRF 방어책이 아니라는 것입니다.
CORS 때문에 악성 사이트가 응답 내용을 읽지 못할 수는 있습니다.
하지만 서버가 요청 자체를 처리해버리면 이미 문제가 발생할 수 있습니다.
예를 들어 악성 사이트가 송금 요청을 보내고, 응답을 읽지 못하더라도 송금이 성공하면 공격은 성공한 것입니다.
CORS로 응답 읽기 차단
→ 하지만 요청이 서버에서 처리되었다면 CSRF 방어 실패
따라서 쿠키 기반 인증에서는 CORS 설정과 별개로 CSRF 방어가 필요합니다.
CSRF 공격 흐름
CSRF 공격 흐름은 다음처럼 정리할 수 있습니다.
- 사용자가 정상 사이트에 로그인합니다.
- 브라우저에 로그인 쿠키가 저장됩니다.
- 사용자가 악성 사이트를 방문합니다.
- 악성 사이트가 정상 사이트로 요청을 보내게 만듭니다.
- 브라우저가 정상 사이트 쿠키를 자동으로 함께 보냅니다.
- 서버가 쿠키만 보고 정상 요청으로 오해합니다.
- 원치 않는 상태 변경이 발생합니다.
사용자 로그인
→ 쿠키 저장
→ 악성 사이트 방문
→ 악성 요청 발생
→ 브라우저가 쿠키 자동 첨부
→ 서버가 요청 처리

CSRF 방어의 핵심 아이디어
CSRF 방어의 핵심은 간단합니다.
서버가 요청을 받을 때, 단순히 쿠키만 확인하는 것이 아니라 정상 페이지에서 생성된 요청인지 확인할 수 있는 추가 증거를 요구하는 것입니다.
대표적인 방법이 CSRF 토큰입니다.
서버가 CSRF 토큰 발급
→ 정상 페이지가 토큰을 포함해 요청
→ 서버가 쿠키와 토큰을 함께 검증
→ 토큰이 없거나 틀리면 거부
공격자는 사용자의 쿠키를 자동으로 보내게 만들 수는 있지만, 정상 사이트가 발급한 CSRF 토큰 값을 알기 어렵습니다.
그래서 서버는 “쿠키는 있지만 CSRF 토큰이 없는 요청”을 의심할 수 있습니다.
CSRF 토큰 방식
CSRF 토큰 방식은 가장 대표적인 방어 방법입니다.
기본 흐름은 다음과 같습니다.
- 서버가 사용자에게 CSRF 토큰을 발급합니다.
- 정상 페이지는 form 또는 요청 헤더에 CSRF 토큰을 포함합니다.
- 사용자가 상태 변경 요청을 보냅니다.
- 서버는 요청의 CSRF 토큰이 유효한지 검증합니다.
- 토큰이 유효하면 요청을 처리합니다.
- 토큰이 없거나 틀리면 요청을 거부합니다.
HTML form에서는 다음처럼 토큰을 hidden input으로 넣을 수 있습니다.
<form method="POST" action="/profile/update/">
<input type="hidden" name="csrfmiddlewaretoken" value="csrf-token-value">
<input type="text" name="nickname">
<button type="submit">저장</button>
</form>
AJAX 요청에서는 보통 헤더에 토큰을 넣습니다.
X-CSRFToken: csrf-token-value
서버는 요청에 포함된 토큰과 서버가 기대하는 토큰을 비교합니다.
토큰이 맞지 않으면 요청을 거부합니다.
왜 공격자는 CSRF 토큰을 알기 어려울까?
CSRF 토큰은 정상 사이트가 만든 페이지나 스크립트에서만 접근할 수 있도록 설계됩니다.
악성 사이트는 다른 origin의 응답 내용을 마음대로 읽을 수 없습니다.
예를 들어 악성 사이트가 은행 페이지를 요청하더라도, 브라우저의 Same-Origin Policy 때문에 응답 HTML 안의 CSRF 토큰을 읽기 어렵습니다.
악성 사이트
→ 은행 페이지 요청 유도 가능
→ 하지만 응답 HTML 안의 CSRF 토큰을 읽기는 어려움
따라서 공격자는 사용자의 쿠키를 함께 보내는 요청은 유도할 수 있어도, 정상 CSRF 토큰까지 포함한 요청을 만들기는 어렵습니다.
물론 사이트에 XSS 취약점이 있다면 이야기가 달라집니다.
XSS로 정상 사이트 안에서 스크립트를 실행할 수 있다면 CSRF 토큰을 읽거나 요청에 포함할 수 있습니다.
그래서 CSRF 방어와 XSS 방어는 함께 필요합니다.
SameSite 쿠키
CSRF 방어에서 중요한 또 다른 개념은 SameSite 쿠키 속성입니다.
SameSite는 브라우저가 cross-site 요청에 쿠키를 보낼지 제어하는 쿠키 옵션입니다.
예를 들어 다음처럼 설정할 수 있습니다.
Set-Cookie: sessionid=abc123; SameSite=Lax; Secure; HttpOnly
대표 값은 다음과 같습니다.
SameSite=Strict
가장 엄격한 설정입니다.
다른 사이트에서 시작된 요청에는 쿠키를 보내지 않습니다.
SameSite=Strict
→ 같은 사이트 요청에만 쿠키 전송
보안은 강하지만 사용자 경험에 영향을 줄 수 있습니다.
SameSite=Lax
대부분의 일반적인 탐색에는 쿠키를 보내지만, 위험한 cross-site 요청에는 쿠키 전송을 제한합니다.
많은 경우 기본값으로 고려할 수 있는 균형 잡힌 설정입니다.
SameSite=Lax
→ 일반 링크 이동 등은 허용
→ 일부 cross-site POST 요청은 제한
SameSite=None
cross-site 요청에도 쿠키를 보냅니다.
이 값을 사용하려면 보통 Secure도 함께 필요합니다.
Set-Cookie: sessionid=abc123; SameSite=None; Secure
프론트엔드와 백엔드가 다른 사이트에 있고, 쿠키 기반 인증을 cross-site로 사용해야 하는 경우 필요할 수 있습니다.
하지만 CSRF 위험을 더 신중하게 관리해야 합니다.
SameSite만으로 충분할까?
SameSite 쿠키는 CSRF 위험을 줄이는 데 큰 도움이 됩니다.
하지만 모든 상황에서 CSRF 토큰을 완전히 대체한다고 생각하면 위험할 수 있습니다.
이유는 다음과 같습니다.
- 브라우저 호환성과 정책 차이가 있을 수 있습니다.
- 복잡한 도메인 구조에서는 same-site와 same-origin을 혼동할 수 있습니다.
- 일부 인증 흐름에서는 SameSite=None이 필요할 수 있습니다.
- 더 강한 방어가 필요한 상태 변경 요청에는 토큰 검증이 여전히 유용합니다.
- 레거시 환경이나 특수한 클라이언트를 고려해야 할 수 있습니다.
그래서 실무에서는 다음처럼 여러 방어를 함께 고려합니다.
CSRF 토큰
+ SameSite 쿠키
+ Origin / Referer 검증
+ 중요한 작업 재인증
+ XSS 방어
보안은 하나의 설정만으로 끝내기보다 여러 계층으로 방어하는 것이 좋습니다.
Origin과 Referer 검증
서버는 요청의 Origin 또는 Referer 헤더를 확인해 요청이 신뢰할 수 있는 출처에서 왔는지 검증할 수 있습니다.
예를 들어 상태 변경 요청이 들어왔을 때 Origin이 우리 사이트인지 확인합니다.
Origin: https://www.example.com
허용된 origin이 아니라면 요청을 거부할 수 있습니다.
허용된 Origin
→ https://www.example.com
요청 Origin
→ https://evil.example.com
결과
→ 거부
Referer 헤더도 비슷하게 사용할 수 있습니다.
다만 헤더가 누락되거나 정책에 따라 제한될 수 있으므로, 일반적으로 CSRF 토큰을 중심으로 두고 Origin 또는 Referer 검증을 보조적으로 사용하는 경우가 많습니다.
안전한 메서드와 위험한 메서드
CSRF 방어에서는 HTTP 메서드도 중요합니다.
일반적으로 GET, HEAD, OPTIONS 같은 메서드는 서버 상태를 변경하지 않아야 합니다.
GET
→ 조회만 수행
POST / PATCH / PUT / DELETE
→ 상태 변경 가능
만약 GET 요청으로 게시글을 삭제한다면 CSRF에 매우 취약해질 수 있습니다.
예를 들어 다음 링크만으로 삭제가 가능하다면 위험합니다.
<img src="https://example.com/posts/1/delete">
브라우저는 이미지를 로드하려고 GET 요청을 보낼 수 있습니다.
따라서 상태를 변경하는 작업은 GET으로 만들지 않아야 합니다.
좋은 설계
→ GET은 조회만
→ 상태 변경은 POST, PATCH, PUT, DELETE 사용
→ 상태 변경 요청에는 CSRF 방어 적용
HTTP 메서드의 의미를 지키는 것도 CSRF 방어의 기본입니다.
CSRF 방어 흐름
CSRF 방어 흐름은 다음처럼 볼 수 있습니다.
정상 페이지 요청
→ 서버가 CSRF 토큰 제공
→ 브라우저가 쿠키 저장
→ 사용자가 상태 변경 요청
→ 요청에 CSRF 토큰 포함
→ 서버가 쿠키와 토큰 검증
→ 유효하면 처리, 아니면 거부

Django의 CSRF 보호
Django는 기본적으로 CSRF 보호 기능을 제공합니다.
Django 프로젝트의 MIDDLEWARE 설정을 보면 보통 다음 미들웨어가 들어 있습니다.
MIDDLEWARE = [
# ...
"django.middleware.csrf.CsrfViewMiddleware",
# ...
]
이 미들웨어는 CSRF 검증을 담당합니다.
Django 템플릿에서 form을 사용할 때는 {% csrf_token %}을 넣습니다.
<form method="post">
{% csrf_token %}
<input type="text" name="title">
<button type="submit">저장</button>
</form>
Django는 이 템플릿 태그를 통해 form 안에 CSRF 토큰을 추가합니다.
브라우저가 form을 제출하면 요청에 CSRF 토큰이 함께 전송되고, Django는 이를 검증합니다.
Django에서 AJAX 요청과 CSRF
Django에서 JavaScript로 POST 요청을 보낼 때는 CSRF 토큰을 헤더에 포함해야 할 수 있습니다.
일반적으로 X-CSRFToken 헤더를 사용합니다.
fetch("/api/posts/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrfToken
},
body: JSON.stringify({
title: "CSRF 이해하기",
content: "쿠키 기반 인증에서 중요한 보안 개념입니다."
})
});
여기서 csrfToken은 쿠키나 HTML에 렌더링된 값을 통해 가져올 수 있습니다.
Django 설정과 프론트엔드 구조에 따라 토큰을 전달하는 방식은 달라질 수 있습니다.
중요한 것은 상태 변경 요청에 CSRF 토큰이 포함되어야 한다는 점입니다.
Django REST Framework와 CSRF
Django REST Framework를 사용할 때 CSRF는 인증 방식에 따라 다르게 체감됩니다.
SessionAuthentication
DRF에서 SessionAuthentication을 사용하면 Django 세션과 쿠키를 기반으로 인증합니다.
이 경우 CSRF 검증이 적용됩니다.
즉, 브라우저에서 세션 쿠키로 인증하는 API라면 CSRF 토큰을 함께 보내야 할 수 있습니다.
SessionAuthentication
→ 쿠키 기반 인증
→ CSRF 검증 필요
Token 또는 JWT 인증
반면 Authorization 헤더에 토큰을 넣는 방식은 쿠키 자동 전송에 의존하지 않습니다.
Authorization: Bearer access-token
이 경우 일반적인 CSRF 위험은 쿠키 기반 인증보다 낮을 수 있습니다.
왜냐하면 악성 사이트가 사용자의 브라우저에게 cross-site 요청을 보내게 하더라도, Authorization 헤더에 access token을 마음대로 넣기 어렵기 때문입니다.
하지만 토큰을 어디에 저장하는지에 따라 XSS 같은 다른 위험이 생길 수 있습니다.
쿠키 기반 세션
→ CSRF 방어 중요
Authorization 헤더 기반 토큰
→ CSRF 위험은 상대적으로 낮지만 XSS와 토큰 저장 방식 주의
인증 방식에 따라 위협 모델이 달라진다는 점을 이해해야 합니다.
SPA와 CSRF
React, Vue, Next.js 같은 SPA와 Django API를 함께 사용할 때 CSRF 설정이 헷갈릴 수 있습니다.
특히 다음 구조에서 자주 고민합니다.
프론트엔드
→ https://www.example.com
백엔드 API
→ https://api.example.com
쿠키 기반 인증을 cross-site로 사용한다면 다음을 함께 고려해야 합니다.
- CORS 설정
credentials: "include"Access-Control-Allow-CredentialsSameSite=None; Secure- CSRF 토큰 전달
- CSRF trusted origins
- HTTPS
- Origin 검증
반대로 JWT를 Authorization 헤더로 보내는 구조라면 CSRF보다 토큰 저장 위치와 XSS 방어가 더 중요해질 수 있습니다.
정답은 하나가 아닙니다.
서비스 구조, 보안 요구사항, 클라이언트 종류, 도메인 구성에 따라 적절한 방식을 선택해야 합니다.
CSRF_TRUSTED_ORIGINS
Django에서 HTTPS 환경이나 다른 origin의 프론트엔드와 연동할 때 CSRF_TRUSTED_ORIGINS 설정이 필요할 수 있습니다.
예를 들어 다음처럼 설정할 수 있습니다.
CSRF_TRUSTED_ORIGINS = [
"https://www.example.com",
"https://admin.example.com",
]
이 설정은 CSRF 검증 과정에서 신뢰할 수 있는 origin을 지정할 때 사용됩니다.
CORS 설정과 이름이 비슷해서 헷갈릴 수 있지만 역할이 다릅니다.
CORS_ALLOWED_ORIGINS
→ 브라우저가 cross-origin 응답을 읽도록 허용할 origin
CSRF_TRUSTED_ORIGINS
→ CSRF 검증에서 신뢰할 수 있는 origin
둘 다 필요할 수 있지만, 서로 같은 설정은 아닙니다.
CSRF 예외 처리는 신중하게
Django에서는 특정 view에 대해 CSRF 검증을 예외 처리할 수 있습니다.
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def webhook_view(request):
...
하지만 csrf_exempt는 신중하게 사용해야 합니다.
일반 사용자 브라우저에서 호출되는 상태 변경 API에 무심코 적용하면 CSRF에 취약해질 수 있습니다.
예외 처리가 필요한 대표적인 경우는 외부 서비스 웹훅입니다.
웹훅은 사용자의 브라우저가 보내는 요청이 아니라 외부 서비스 서버가 보내는 요청입니다.
이 경우 CSRF 토큰을 사용할 수 없으므로 CSRF 예외를 적용하고, 대신 웹훅 서명 검증을 해야 합니다.
일반 사용자 요청
→ CSRF 토큰 검증
외부 웹훅 요청
→ CSRF 예외 가능
→ 대신 서명 검증 필수
CSRF 예외는 “보안을 끄는 것”에 가깝기 때문에, 대체 검증 수단이 있을 때만 제한적으로 사용해야 합니다.
중요한 작업에는 추가 확인이 필요하다
CSRF 토큰과 SameSite 쿠키를 설정했다고 해서 모든 위험이 사라지는 것은 아닙니다.
특히 중요한 작업에는 추가 확인이 필요할 수 있습니다.
예를 들어 다음 작업은 더 강한 보호가 필요할 수 있습니다.
- 비밀번호 변경
- 이메일 주소 변경
- 결제 수단 등록
- 송금
- 계정 삭제
- 관리자 권한 변경
- API 키 발급
이런 작업에는 다음을 추가로 요구할 수 있습니다.
- 현재 비밀번호 재입력
- 2단계 인증
- 이메일 확인
- 관리자 승인
- 짧은 만료 시간의 재인증 토큰
CSRF 방어는 중요한 보안 계층이지만, 고위험 작업에는 추가 보호 장치도 고려해야 합니다.
CSRF 테스트 방법
CSRF 방어가 제대로 동작하는지 테스트하는 것도 중요합니다.
예를 들어 Django에서 CSRF 토큰 없이 POST 요청을 보냈을 때 거부되는지 확인할 수 있습니다.
테스트 관점에서는 다음을 확인합니다.
[ ] CSRF 토큰 없는 POST 요청이 거부되는가?
[ ] 잘못된 CSRF 토큰이 거부되는가?
[ ] 올바른 CSRF 토큰이 있으면 요청이 성공하는가?
[ ] GET 요청으로 상태 변경이 발생하지 않는가?
[ ] CSRF 예외 view가 불필요하게 많지 않은가?
[ ] 중요한 API에 세션 인증을 사용하는 경우 CSRF가 적용되는가?
브라우저 개발자 도구에서 요청 헤더와 쿠키를 확인하는 것도 도움이 됩니다.
Cookie: csrftoken=...
X-CSRFToken: ...
Origin: https://www.example.com
CSRF 방어 체크리스트
쿠키 기반 인증을 사용하는 서비스라면 아래 항목을 점검하면 좋습니다.
[ ] 상태 변경 요청에 CSRF 토큰 검증이 적용되는가?
[ ] GET 요청이 서버 상태를 변경하지 않는가?
[ ] 세션 쿠키에 SameSite 설정이 적용되어 있는가?
[ ] 운영 환경에서 Secure 쿠키를 사용하는가?
[ ] 중요한 요청에서 Origin 또는 Referer 검증을 고려했는가?
[ ] CORS와 CSRF를 혼동하고 있지 않은가?
[ ] CSRF 예외 처리된 view를 점검했는가?
[ ] 웹훅은 CSRF 대신 서명 검증을 사용하는가?
[ ] XSS 방어도 함께 고려하고 있는가?
[ ] 비밀번호 변경, 계정 삭제 같은 고위험 작업에는 추가 확인이 있는가?
CSRF는 설정 하나로 끝내기보다, 인증 방식과 도메인 구조, 쿠키 정책을 함께 보면서 점검해야 합니다.
CSRF에서 자주 하는 실수
1. CORS를 설정했으니 CSRF도 막힌다고 생각하는 경우
CORS와 CSRF는 다릅니다.
CORS는 응답을 읽을 수 있는지에 관한 정책이고, CSRF는 원치 않는 요청이 처리되는 문제입니다.
2. 모든 POST API에 csrf_exempt를 붙이는 경우
개발 중 CSRF 오류가 귀찮아서 csrf_exempt를 남발하면 위험합니다.
CSRF 검증을 끄기보다, 올바르게 토큰을 전달하도록 수정해야 합니다.
3. GET 요청으로 상태를 변경하는 경우
GET 요청은 조회 용도로만 사용해야 합니다.
삭제, 수정, 결제 같은 상태 변경을 GET으로 처리하면 위험합니다.
4. SameSite만 믿고 토큰 검증을 완전히 제거하는 경우
SameSite는 강력한 방어 수단이지만 모든 상황에서 충분하다고 단정하기 어렵습니다.
서비스 구조에 따라 CSRF 토큰을 함께 사용하는 것이 좋습니다.
5. 쿠키 기반 인증과 토큰 기반 인증의 위험을 구분하지 않는 경우
쿠키 기반 인증은 CSRF를 특히 주의해야 합니다.
Authorization 헤더 기반 토큰은 CSRF 위험은 낮을 수 있지만, 토큰 저장 방식과 XSS 방어가 중요합니다.
6. 웹훅에서 CSRF와 서명 검증을 혼동하는 경우
웹훅은 브라우저 form 요청이 아니므로 CSRF 토큰 방식이 맞지 않을 수 있습니다.
대신 외부 서비스가 제공하는 서명 검증을 구현해야 합니다.
정리
CSRF는 사용자가 로그인한 상태를 악용해, 사용자가 의도하지 않은 요청을 보내게 만드는 공격입니다.
특히 쿠키 기반 인증에서는 브라우저가 쿠키를 자동으로 전송하기 때문에 CSRF 방어가 중요합니다.
핵심을 정리하면 다음과 같습니다.
- CSRF는 Cross-Site Request Forgery의 약자이며 사이트 간 요청 위조를 의미합니다.
- 공격자는 사용자의 쿠키 값을 몰라도, 사용자의 브라우저가 쿠키를 자동으로 보내는 특성을 악용할 수 있습니다.
- CSRF는 사용자가 의도하지 않은 상태 변경 요청을 서버가 처리하게 만드는 것이 핵심입니다.
- CORS는 CSRF 방어책이 아닙니다.
- XSS와 CSRF는 다른 공격이지만 함께 고려해야 합니다.
- CSRF 토큰은 정상 페이지에서 생성된 요청인지 확인하기 위한 대표적인 방어 수단입니다.
- SameSite 쿠키는 cross-site 요청에서 쿠키 전송을 제한해 CSRF 위험을 줄일 수 있습니다.
- 상태를 변경하는 작업은 GET 요청으로 만들면 안 됩니다.
- Django는
CsrfViewMiddleware와{% csrf_token %}을 통해 기본적인 CSRF 보호를 제공합니다. - DRF에서 SessionAuthentication을 사용하면 CSRF 검증을 고려해야 합니다.
- JWT를 Authorization 헤더로 보내는 방식은 CSRF 위험이 상대적으로 낮을 수 있지만, XSS와 토큰 저장 방식에 주의해야 합니다.
csrf_exempt는 필요한 경우에만 제한적으로 사용해야 하며, 웹훅에서는 대신 서명 검증이 필요합니다.- 중요한 작업에는 현재 비밀번호 재입력이나 2단계 인증 같은 추가 보호도 고려할 수 있습니다.
CSRF는 처음에는 복잡해 보이지만, 핵심은 “쿠키만으로 요청을 신뢰하면 안 된다”는 점입니다.
사용자의 브라우저가 자동으로 쿠키를 보내더라도, 서버는 그 요청이 정말 사용자의 의도로 발생한 정상 요청인지 확인해야 합니다.
참고 링크
'개발 기초' 카테고리의 다른 글
| CORS 이해하기: 프론트엔드와 백엔드가 다른 도메인일 때 생기는 문제 (0) | 2026.05.19 |
|---|---|
| REST API 이해하기: HTTP 메서드, 상태 코드, JSON 응답까지 (0) | 2026.05.11 |
| 파이썬 GIL 이해하기: 멀티스레딩이 항상 빨라지지 않는 이유 (0) | 2026.05.11 |
| Docker 이해하기: 이미지, 컨테이너, Dockerfile, Compose까지 (0) | 2026.05.11 |
| 로드밸런서 이해하기: 서버 트래픽을 나누고 장애를 줄이는 방법 (0) | 2026.05.11 |
