개발철학&개발용어

CSRF란? 개념부터 방어까지 쉽게 정리

CSRF란? 개념부터 방어까지 쉽게 정리

 

CSRF(크로스 사이트 요청 위조)는 웹 보안을 공부하다 보면 XSS와 함께 가장 먼저 마주치는 공격입니다. 그런데 이름이 낯설다 보니 "결국 세션을 훔치는 거 아니야?"라고 오해하기 쉽습니다. 사실 CSRF 공격은 세션을 탈취하는 게 아니라, 이미 로그인한 사용자의 브라우저를 공격자가 대신 조종해서 사용자가 원하지 않은 요청을 몰래 보내게 만드는 방식입니다. 이 미묘한 차이를 이해하면 나머지는 의외로 쉽게 풀립니다.

"CSRF랑 XSS가 매번 헷갈려요. 뭐가 다른 거죠?"
"SameSite 쿠키만 걸어두면 CSRF는 끝난 거 아닌가요?"
"요즘 브라우저가 알아서 막아준다던데, 그래도 CSRF 토큰을 써야 하나요?"

웹 개발을 하다 보면 한 번쯤 던지게 되는 질문들입니다. 답은 대부분 "반은 맞고 반은 틀립니다"에 가깝습니다. 브라우저가 예전보다 훨씬 많이 막아주는 건 사실이지만, 그렇다고 방어를 손 놓아도 되는 건 아니거든요. 특히 2026년 현재는 브라우저마다 동작이 달라서, 어디까지가 기본으로 막히고 어디부터 직접 막아야 하는지 선을 아는 게 중요해졌습니다.

이 글은 CSRF를 처음 접하는 분을 위해 개념부터 시작합니다. 공격이 어떤 원리로 성립하는지, XSS와는 뭐가 다른지, 그리고 실무에서 실제로 쓰는 방어법(CSRF 토큰과 SameSite 쿠키)이 각각 무엇을 막고 무엇을 못 막는지까지 차근차근 정리해 보겠습니다.

목차

  1. CSRF란 무엇인가 - 세션 탈취와의 차이
  2. CSRF 공격이 성립하는 세 가지 조건
  3. 실제 CSRF 공격은 어떻게 일어날까
  4. CSRF와 XSS는 어떻게 다른가
  5. CSRF 방어법 1 - CSRF 토큰
  6. CSRF 방어법 2 - SameSite 쿠키
  7. 토큰과 SameSite, 지금도 둘 다 필요할까
  8. 자주 묻는 질문
  9. 정리하며

1. CSRF란 무엇인가 - 세션 탈취와의 차이

CSRF는 Cross-Site Request Forgery의 약자로, 우리말로는 흔히 "크로스 사이트 요청 위조"라고 부릅니다. 이름을 하나씩 뜯어보면 성격이 보입니다. Cross-Site는 공격이 다른 사이트에서 시작된다는 뜻이고, Request Forgery는 요청을 위조한다는 뜻입니다. 즉 공격자의 사이트에서 출발한 가짜 요청을, 마치 사용자가 직접 보낸 것처럼 꾸며 신뢰받는 사이트로 날리는 공격입니다.

여기서 많은 분이 헷갈리는 지점이 하나 있습니다. CSRF는 사용자의 비밀번호나 세션 쿠키 자체를 빼내는 공격이 아니라는 점입니다. 공격자는 사용자의 쿠키 값을 알 필요조차 없습니다. 그럴 필요가 없거든요.

이유는 간단합니다. 브라우저는 특정 사이트로 요청을 보낼 때, 그 사이트의 쿠키를 자동으로 함께 실어 보냅니다. 사용자가 A은행에 로그인한 상태라면, A은행으로 가는 모든 요청에는 세션 쿠키가 딸려 갑니다. 그 요청이 A은행 사이트에서 시작됐든, 공격자의 낚시 페이지에서 시작됐든 브라우저는 구분하지 않습니다. 이 "자동으로 쿠키를 붙여주는" 습성을 악용하는 것이 바로 CSRF입니다.

💡 한 줄 요약
CSRF는 "인증 정보를 훔치는" 공격이 아니라, "이미 인증된 사용자를 시켜서 대신 일을 하게 만드는" 공격입니다. 보안 용어로는 혼동된 대리인(confused deputy) 문제라고 부릅니다. 서버 입장에서는 진짜 사용자가 보낸 정상 요청과, 속아서 보낸 위조 요청을 겉만 봐서는 구별할 수 없기 때문입니다.

2. CSRF 공격이 성립하는 세 가지 조건

CSRF는 아무 사이트에나 통하는 만능 공격이 아닙니다. 몇 가지 조건이 맞아떨어져야 성립합니다. 반대로 말하면, 이 조건 중 하나만 깨도 방어가 됩니다. 그래서 방어를 이해하기 전에 조건부터 보는 게 순서입니다.

조건 1 - 쿠키 기반 인증을 쓴다

공격의 핵심 연료는 "브라우저가 자동으로 실어 보내는 인증 정보"입니다. 세션 쿠키처럼 요청마다 자동으로 붙는 방식을 쓸 때 CSRF가 성립합니다. 반대로 인증 토큰을 자바스크립트가 직접 헤더에 넣어 보내는 방식(예: Authorization 헤더에 담는 Bearer 토큰)은, 다른 사이트에서 그 헤더를 임의로 붙일 수 없기 때문에 기본적으로 CSRF에 훨씬 강합니다.

조건 2 - 사용자가 로그인된 상태다

당연한 이야기지만, 위조 요청이 힘을 가지려면 사용자가 대상 사이트에 로그인되어 있어야 합니다. 로그아웃 상태라면 쿠키가 붙어도 서버가 거부하니까요. 그래서 CSRF는 은행, 쇼핑몰, 관리자 페이지처럼 "로그인 상태로 오래 머무는 서비스"를 노립니다.

조건 3 - 요청을 예측할 수 있다

공격자는 위조할 요청의 형태를 미리 알아야 합니다. 어떤 주소로, 어떤 값을 보내면 송금이 되는지 알아야 그 요청을 흉내 낼 수 있으니까요. 그래서 요청에 공격자가 도저히 알 수 없는 값(뒤에 나올 CSRF 토큰)이 하나만 끼어 있어도 위조가 어려워집니다.

3. 실제 CSRF 공격은 어떻게 일어날까

말로만 들으면 감이 안 오니, 가장 고전적인 시나리오를 예로 들어보겠습니다. 사용자가 인터넷 뱅킹에 로그인한 채로, 우연히 공격자가 만든 페이지에 접속했다고 해봅시다.

GET 요청을 노리는 경우

만약 은행이 송금 같은 중요한 작업을 GET 요청으로 처리한다면(절대 그러면 안 되지만) 공격은 놀랄 만큼 쉬워집니다. 공격자는 페이지에 이런 이미지 태그 하나만 심어두면 됩니다.

<img src="https://bank.com/transfer?to=attacker&amount=1000000">

사용자 눈에는 아무것도 안 보이지만, 브라우저는 이 이미지를 불러오려고 해당 주소로 요청을 보냅니다. 이때 은행 쿠키가 자동으로 딸려 가면서, 서버 입장에서는 "로그인한 사용자가 송금을 요청했다"고 착각하게 됩니다. 그래서 상태를 바꾸는 작업은 절대 GET으로 처리하지 말라는 원칙이 여기서 나옵니다.

POST 요청을 노리는 경우

제대로 만든 사이트는 송금을 POST로 처리합니다. 그래도 방심할 수 없습니다. 공격자는 눈에 보이지 않는 폼을 만들고, 페이지가 열리자마자 자바스크립트로 자동 제출시킬 수 있습니다.

<form action="https://bank.com/transfer" method="POST">
  <input type="hidden" name="to" value="attacker">
  <input type="hidden" name="amount" value="1000000">
</form>
<script>document.forms[0].submit();</script>

사용자는 버튼을 누른 적도 없는데 폼이 스스로 제출되고, 쿠키는 여전히 자동으로 따라갑니다. 이것이 CSRF가 무서운 이유입니다. 사용자는 자기가 무슨 일에 가담했는지 전혀 모르니까요.

📌 짚고 넘어갈 점
CSRF는 요청을 "보내는" 것까지만 할 수 있고, 그 응답을 "읽지는" 못합니다. 브라우저의 동일 출처 정책(Same-Origin Policy) 때문에 공격자 사이트는 은행이 돌려준 응답 내용을 볼 수 없거든요. 그래서 CSRF는 데이터를 훔쳐보는 공격이 아니라, 송금·비밀번호 변경·설정 변경처럼 상태를 바꾸는 작업을 노리는 공격입니다.

4. CSRF와 XSS는 어떻게 다른가

입문자가 가장 많이 헷갈려하는 두 공격입니다. 이름도 비슷하고 둘 다 웹 보안 단골이라 뒤섞이기 쉬운데, 사실 노리는 지점이 정반대입니다. CSRF는 서버가 "이 요청이 진짜 사용자 의도인지" 구별 못 하는 허점을 노리고, XSS는 브라우저가 "이 스크립트가 믿을 만한지" 구별 못 하는 허점을 노립니다.

구분 CSRF XSS
공격 대상 서버의 신뢰 (인증된 요청) 사용자의 브라우저 (스크립트 실행)
핵심 원리 신뢰받는 사이트에 위조 요청을 보냄 신뢰받는 사이트에 악성 스크립트를 심음
응답을 읽을 수 있나 읽지 못함 (요청만 가능) 읽을 수 있음 (쿠키 탈취까지 가능)
주요 방어 CSRF 토큰, SameSite 쿠키 입력값 이스케이프, CSP

둘의 관계에서 꼭 알아야 할 사실이 하나 있습니다. XSS가 뚫리면 CSRF 방어는 사실상 무력화됩니다. 악성 스크립트가 페이지 안에서 돌아가면 CSRF 토큰이든 뭐든 다 읽어낼 수 있으니까요. 그래서 두 방어는 별개가 아니라, XSS를 먼저 막아야 CSRF 방어도 의미가 있다는 순서로 이해하는 게 맞습니다.

5. CSRF 방어법 1 - CSRF 토큰

CSRF 토큰은 가장 오래됐고 가장 널리 쓰이는 방어법입니다. 원리는 앞서 말한 "조건 3(요청을 예측할 수 있다)"을 무너뜨리는 것입니다. 서버가 예측 불가능한 임의의 값을 하나 만들어서, 상태를 바꾸는 모든 요청에 그 값을 함께 보내도록 요구합니다. 공격자는 이 값을 알 방법이 없으니 요청을 위조해도 서버가 걸러냅니다.

동기화 토큰 패턴

가장 정석적인 방식입니다. 사용자가 폼을 받을 때 서버가 임의의 토큰을 만들어 세션에 저장해 두고, 동시에 폼 안의 숨은 필드에도 넣어 보냅니다. 사용자가 폼을 제출하면 서버는 넘어온 토큰과 세션에 저장한 토큰이 같은지 확인합니다. 이 방식은 서버가 상태(세션)를 들고 있어야 해서 "상태 유지형(stateful)"이라고 부릅니다.

이중 제출 쿠키 패턴

서버가 세션을 유지하기 부담스러울 때 쓰는 대안입니다. 임의의 값을 쿠키에도 넣고, 요청 파라미터에도 넣게 한 뒤 두 값이 일치하는지만 확인합니다. 서버가 별도로 저장할 게 없어서 "무상태형(stateless)"이라 부릅니다. 다만 하위 도메인에서 쿠키를 덮어쓸 수 있는 등 허점이 있어, 최근에는 서버만 아는 비밀 키로 서명한 형태를 권장합니다.

커스텀 요청 헤더

요즘처럼 폼 대신 자바스크립트로 API를 호출하는 구조에서 특히 잘 맞는 방식입니다. 자바스크립트가 요청에 임의의 커스텀 헤더를 하나 붙이게 하고, 서버는 그 헤더가 있는지 확인합니다. 다른 사이트에서는 브라우저 정책상 이런 커스텀 헤더를 임의로 붙일 수 없기 때문에, 별도 토큰 없이도 방어가 됩니다.

💡 직접 만들기 전에
CSRF 토큰은 대부분의 웹 프레임워크에 이미 내장되어 있습니다. Spring Security, Django, Rails 같은 프레임워크는 기본 CSRF 보호 기능을 제공하니, 손수 토큰 생성기를 짜기 전에 프레임워크가 지원하는 기능부터 켜서 쓰는 걸 OWASP도 강하게 권합니다.

6. CSRF 방어법 2 - SameSite 쿠키

SameSite는 비교적 최근에 무게가 실린 방어법입니다. 쿠키를 설정할 때 SameSite라는 속성을 붙이면, 브라우저가 "다른 사이트에서 시작된 요청에는 이 쿠키를 붙일지 말지"를 결정합니다. CSRF의 연료가 "자동으로 붙는 쿠키"였으니, 그 연료를 끊어버리는 접근인 셈입니다.

SameSite 값은 세 가지입니다.

- Strict: 다른 사이트에서 온 요청에는 어떤 경우에도 쿠키를 보내지 않습니다. 가장 안전하지만, 외부 링크를 타고 들어온 사용자가 로그아웃된 것처럼 보이는 불편이 생깁니다.
- Lax: 링크 클릭 같은 최상위 이동(top-level navigation)에는 쿠키를 보내지만, 다른 사이트에서 온 POST 요청이나 이미지·iframe 같은 백그라운드 요청에는 보내지 않습니다. 보안과 사용성의 균형이 좋아 대부분의 서비스에 권장됩니다.
- None: 모든 크로스 사이트 요청에 쿠키를 보냅니다. 사실상 SameSite 보호를 끄는 것이라, 반드시 Secure 속성(HTTPS)과 함께 써야 합니다.

여기서 입문자가 흔히 오해하는 부분이 있습니다. "요즘 브라우저는 알아서 Lax로 막아준다"는 말인데, 절반만 맞습니다. Chrome·Edge·Opera는 SameSite를 명시하지 않으면 기본으로 Lax를 적용하지만, Firefox와 Safari는 이 기본값을 그대로 따르지 않고 각자의 추적 방지 방식을 씁니다. 브라우저에 따라 동작이 갈리는 겁니다.

📌 "명시하지 않은 Lax"의 함정
브라우저가 자동으로 적용하는 Lax와, 개발자가 직접 SameSite=Lax를 설정한 것은 같지 않습니다. Chrome은 SSO(간편 로그인) 흐름이 깨지지 않도록, SameSite를 지정하지 않은 쿠키에 대해서는 발급 후 약 2분(120초) 동안 최상위 POST 요청에 쿠키를 붙여주는 예외를 둡니다. 이 2분짜리 창을 노린 CSRF가 실제로 존재합니다. 그래서 브라우저 기본값에 기대지 말고, 쿠키에 SameSite 값을 직접 명시하는 게 안전합니다.

7. 토큰과 SameSite, 지금도 둘 다 필요할까

이 질문이 이 글에서 가장 실무적인 대목입니다. SameSite가 웬만한 크로스 사이트 요청을 막아주는데, 굳이 CSRF 토큰까지 붙여야 하느냐는 것이죠. 결론부터 말하면, "상황에 따라 다르지만 대체로 둘 다 두는 게 안전합니다".

SameSite=Lax는 CSRF의 가장 흔한 경로인 크로스 사이트 폼 전송과 AJAX 요청을 잘 막아줍니다. 만약 상태를 바꾸는 작업을 모두 POST·PUT·DELETE로 처리하고, 세션 쿠키에 SameSite=Lax를 명시했다면 상당히 튼튼한 방어가 됩니다. 반대로 GET으로 상태를 바꾸는 코드가 남아 있다면 Lax는 그걸 막아주지 못합니다.

그런데 SameSite에는 앞서 본 브라우저별 편차, 2분 예외 같은 빈틈이 있고, 같은 도메인의 하위 도메인에서 오는 요청은 여전히 쿠키를 실어 보낼 수 있습니다. 그래서 OWASP는 SameSite를 CSRF 토큰의 대체재가 아니라 보조 수단으로 보고, 쿠키 기반 인증을 쓰는 웹앱에서는 토큰을 여전히 필수로 권합니다. 하나가 뚫려도 다른 하나가 남도록 겹겹이 쌓는 심층 방어의 개념입니다.

방어 수단 강점 한계
CSRF 토큰 공격자가 값을 알 수 없어 위조 자체가 어려움 XSS가 뚫리면 무력화, 구현 부담
SameSite 쿠키 설정 한 줄로 흔한 경로 대부분 차단 브라우저별 편차, 2분 예외, 하위 도메인

8. 자주 묻는 질문

CSRF와 XSS 중 뭐가 더 위험한가요?

일반적으로는 XSS가 더 위험하다고 봅니다. XSS는 스크립트를 실행할 수 있어서 쿠키 탈취, 화면 조작, 나아가 CSRF 방어 무력화까지 가능하기 때문입니다. CSRF는 요청을 위조할 뿐 응답을 읽지 못한다는 점에서 상대적으로 제한적입니다. 다만 위험의 크기는 서비스가 무엇을 다루느냐에 따라 달라지니 단정하긴 어렵습니다.

REST API만 쓰면 CSRF는 신경 안 써도 되나요?

인증을 쿠키가 아니라 Authorization 헤더의 토큰으로 처리한다면 CSRF 위험은 크게 줄어듭니다. 하지만 API가 쿠키 인증을 함께 쓰거나, text/plain 같은 단순 콘텐츠 타입을 허용한다면 여전히 취약할 수 있습니다. "API라서 안전하다"기보다 "인증 방식이 무엇이냐"로 판단하는 게 정확합니다.

CSRF 토큰을 쿠키에만 담아도 되나요?

토큰을 쿠키 하나에만 담으면 안 됩니다. 쿠키는 위조 요청에도 자동으로 붙기 때문에, 그것만으로는 정상 요청과 위조 요청을 구별할 수 없습니다. 동기화 토큰이라면 쿠키가 아닌 폼 필드나 커스텀 헤더로 돌려받아야 하고, 이중 제출 방식이라면 쿠키와 요청 파라미터 두 곳의 값을 비교해야 합니다.

GET 요청에 CSRF 토큰을 넣으면 안 되나요?

넣지 않는 걸 권합니다. GET 요청은 브라우저 방문 기록, 서버 로그, Referer 헤더 등 여러 곳에 URL이 남기 때문에, 토큰이 그 경로로 새어 나갈 수 있습니다. 애초에 상태를 바꾸는 작업은 GET으로 처리하지 않는 것이 원칙이라, 이 문제도 자연스럽게 피하게 됩니다.

9. 정리하며

CSRF의 핵심은 처음에 짚은 그 문장 하나로 돌아옵니다. 인증 정보를 훔치는 공격이 아니라, 이미 인증된 사용자를 속여서 대신 요청하게 만드는 공격이라는 점입니다. 이 성격을 이해하면 방어의 방향도 분명해집니다. "요청이 진짜 사용자의 의도인지"를 서버가 확인할 수 있게 만들면 되니까요.

실무에서는 프레임워크가 제공하는 CSRF 토큰을 켜고, 세션 쿠키에 SameSite=Lax를 명시하고, 상태를 바꾸는 작업은 반드시 POST 계열로 처리하는 조합이 무난합니다. 그리고 이 모든 방어의 전제가 XSS를 막는 것이라는 점도 잊지 않는 게 좋겠습니다. 브라우저가 예전보다 훨씬 든든해진 건 사실이지만, 어디까지 알아서 막아주는지 그 경계를 아는 개발자와 모르는 개발자의 차이는 앞으로도 계속 벌어질 겁니다.

📚 함께 보면 좋은 글

 

WAF(웹방화벽) 뜻 쉽게 설명해드림! 우리 회사 협업툴이 해킹당하지 않는 진짜 이유

최근 기업의 업무 환경이 디지털로 전면 전환되면서 협업툴의 사용 빈도가 폭발적으로 증가하고 있습니다.하지만 이와 동시에 기업의 핵심 데이터를 집요하게 노리는 웹 해킹 공격도 날이 갈수

devwar.tistory.com

 

SSL 인증서 유효기간 단축 - 1년에서 47일로, 무료 SSL 인증서 대응법

SSL 인증서 유효기간이 점점 짧아지고 있습니다. 그동안 약 1년(398일)이던 공개 TLS 인증서 최대 수명이 2026년 200일, 2027년 100일, 그리고 2029년에는 47일까지 단계적으로 줄어듭니다. 인증서를 수동

devwar.tistory.com

 

우분투는 어쩌다 서버의 기본값이 됐을까 - CentOS를 떠나 우분투로 온 개발자 이야기

우분투(Ubuntu)는 오늘날 리눅스 서버와 클라우드에서 사실상 기본값처럼 쓰입니다. AWS, 구글 클라우드, 애저의 이미지 목록을 열면 가장 먼저 눈에 띄고, 새 프로젝트에서 서버 운영체제를 고를

devwar.tistory.com

 

리눅스 계보도 정복하기: 유닉스에서 시작된 오픈소스 언어의 거대한 역사

우리가 흔히 '리눅스'라고 부르는 운영체제는 사실 단일한 존재가 아닙니다. 🚀 마치 언어에 여러 방언이 있듯이, 리눅스는 수백 가지의 '배포판'이라는 형태로 존재하며 각기 다른 특징과 목적

devwar.tistory.com

 

왜 서버 컴퓨터는 인텔 CPU만 쓸까? - 13년차 개발자가 풀어보는 x86과 ARM의 진짜 차이

서버 컴퓨터는 왜 거의 다 인텔 CPU를 쓸까요? 클라우드에 서버 한 대 올려보면 기본값이 인텔(혹은 x86)인 경우가 대부분이고, 막상 ARM 인스턴스를 골랐다가 프로그램이 안 깔려서 되돌아온 경험,

devwar.tistory.com

반응형