웹사이트를 운영하거나 개발하시는 분이라면 한 번쯤 'CSRF'라는 용어를 들어보셨을 거예요. "도대체 CSRF가 뭐길래 이렇게 중요하다고 하는 걸까?" 하는 의문을 가지셨다면, 지금부터 제가 친절하게 설명해드릴게요! 😊
오늘은 웹 보안의 핵심 개념 중 하나인 CSRF(Cross-Site Request Forgery, 사이트 간 요청 위조) 공격에 대해 알아보고, 실제로 어떻게 방어할 수 있는지까지 완벽하게 정리해드리겠습니다.
🤔 CSRF 공격이란? 쉽게 이해하기
CSRF는 한마디로 **"사용자가 의도하지 않은 요청을 브라우저가 자동으로 실행하게 만드는 공격"**입니다.
여러분이 은행 사이트에 로그인한 상태에서, 공격자가 만든 악성 사이트를 방문하게 되면 어떤 일이 벌어질까요? 놀랍게도 여러분의 은행 계좌에서 공격자의 계좌로 돈이 이체될 수 있습니다! 😱
가장 충격적인 건, 여러분은 아무 것도 하지 않았는데 이런 일이 발생한다는 거예요.
실제 사례로 보는 CSRF의 위험성
2008년 한국에서 발생했던 옥션 개인정보 유출 사건을 기억하시나요? 당시 1,863만 명의 회원 정보가 유출되었는데, 이 사건에서도 CSRF 공격 기법이 사용되었습니다. 공격자는 CSRF를 이용해 관리자 계정을 탈취했고, 결과적으로 막대한 피해가 발생했죠.
이처럼 CSRF는 단순한 이론이 아니라, 실제로 막대한 피해를 일으킬 수 있는 심각한 보안 위협입니다.
🎯 CSRF 공격은 어떻게 작동할까?
CSRF 공격의 메커니즘을 단계별로 살펴볼게요.
1단계: 사용자가 정상 사이트에 로그인
먼저 사용자가 A 서비스(예: 은행 사이트)에 로그인합니다. 서버는 세션을 유지하기 위해 브라우저에 쿠키를 저장합니다.
2단계: 악성 사이트 방문
사용자가 로그인한 상태로 다른 웹사이트, 특히 공격자가 만든 악성 사이트를 방문합니다. 이메일의 링크를 클릭하거나, SNS의 광고를 클릭하는 것만으로도 충분합니다.
3단계: 자동 요청 발생
악성 사이트에는 A 서비스로 향하는 폼 전송이나 스크립트가 숨어있습니다. 사용자가 버튼을 누르거나 심지어 페이지를 열기만 해도, 브라우저는 자동으로 A 서비스의 쿠키를 함께 첨부하여 요청을 보냅니다.
4단계: 서버의 오인
A 서비스 입장에서는 "쿠키가 붙어 있으니 정상 사용자 요청"으로 오인하고, 계좌 이체나 비밀번호 변경 같은 크리티컬한 작업을 수행하게 됩니다.
핵심 포인트 ⚠️
중요한 점은 브라우저가 알아서 쿠키를 붙여주는 기본 동작 때문에, 사용자가 의도하지 않은 요청도 서버 입장에서는 정상적인 요청처럼 보인다는 거예요!
💡 CSRF 공격이 성공하기 위한 조건
모든 웹사이트가 CSRF 공격에 취약한 건 아니에요. 다음 조건들이 충족되어야 공격이 가능합니다:
1. 쿠키 기반 인증 사용
- 서버가 세션 정보를 쿠키로 관리하는 경우
2. 사용자가 이미 인증된 상태
- 로그인이 되어있고, 세션이 유효한 상태
3. 공격자가 요청 패턴을 파악
- 비밀번호 변경, 계좌 이체 등의 API 엔드포인트를 알고 있는 경우
4. 악성 사이트에 접속 유도
- 피싱 메일, SNS 광고, 악성 게시글 등을 통한 유입
이 조건들이 생각보다 쉽게 충족된다는 게 CSRF 공격의 무서운 점입니다! 😰
🛡️ CSRF 공격 방어 방법 완벽 정리
자, 이제 가장 중요한 부분입니다. CSRF 공격을 어떻게 막을 수 있을까요?
방법 1: CSRF 토큰 사용 (가장 강력한 방법!) ✨
CSRF 토큰은 서버가 생성한 랜덤한 문자열로, 각 사용자의 세션마다 고유하게 발급됩니다.
작동 원리
- 사용자가 페이지를 요청하면, 서버는 랜덤 토큰을 생성하여 세션에 저장
- 이 토큰을 폼의 hidden 필드나 헤더에 포함시켜 클라이언트에 전달
- 사용자가 폼을 제출할 때, 토큰도 함께 전송
- 서버는 받은 토큰과 세션에 저장된 토큰을 비교
- 일치하면 정상 처리, 불일치하면 403 에러로 차단
왜 효과적일까?
악성 사이트는 사용자의 쿠키는 알 수 있지만, 세션에 저장된 CSRF 토큰은 알 수 없습니다. 브라우저의 동일 출처 정책(SOP) 때문에 JavaScript로 다른 도메인의 토큰을 읽을 수 없기 때문이죠!
// 예시: 폼에 CSRF 토큰 포함하기
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="랜덤토큰값">
<input type="text" name="amount" value="10000">
<button type="submit">이체하기</button>
</form>
방법 2: SameSite 쿠키 속성 설정 🍪
SameSite는 쿠키 속성 중 하나로, 크로스 사이트 요청 시 쿠키 전송을 제어합니다.
세 가지 옵션
- Strict: 가장 강력한 보호. 크로스 사이트 요청 시 쿠키를 절대 전송하지 않음
- Lax: 기본값. 안전한 GET 요청과 최상위 탐색에서만 쿠키 전송 허용
- None: 모든 크로스 사이트 요청에 쿠키 전송 (Secure 속성 필수)
// 예시: SameSite 쿠키 설정
Set-Cookie: sessionId=abc123; SameSite=Strict; Secure; HttpOnly
실무 팁 💡
- 은행 같은 보안이 중요한 사이트: Strict 사용
- 일반 서비스: Lax가 보안과 사용성의 균형점
- 2025년 현재, 대부분의 모던 브라우저가 SameSite를 지원합니다!
방법 3: Referer 헤더 검증 📋
HTTP 요청 헤더의 Referer 필드를 확인하여, 요청이 올바른 도메인에서 왔는지 검증하는 방법입니다.
장점
- 구현이 간단하고 빠름
- 소규모 웹사이트에 적합
단점
- Referer는 조작이 가능하여 완벽한 방어가 어려움
- 일부 브라우저나 보안 설정에서 Referer를 보내지 않을 수 있음
// 예시: Referer 검증 코드
String referer = request.getHeader("Referer");
String host = request.getHeader("host");
if (referer == null || !referer.contains(host)) {
// 거부 처리
response.sendRedirect("/error");
}
방법 4: GET/POST 메서드 구분 🔀
데이터를 변경하는 작업은 반드시 POST, PUT, DELETE 등의 메서드를 사용하고, GET은 조회 용도로만 사용합니다.
왜냐하면 <img> 태그나 <a> 태그를 통한 CSRF 공격은 대부분 GET 요청이기 때문이죠!
// ❌ 나쁜 예시 - GET으로 데이터 변경
app.get('/delete-account', (req, res) => {
// 계정 삭제 로직
});
// ✅ 좋은 예시 - POST로 데이터 변경
app.post('/delete-account', csrfProtection, (req, res) => {
// 계정 삭제 로직
});
방법 5: CAPTCHA 추가 인증 🤖
중요한 작업(비밀번호 변경, 계좌 이체 등)에는 CAPTCHA나 재인증을 요구하여 추가 보안층을 만듭니다.
사용자 경험(UX)은 다소 떨어질 수 있지만, 보안이 최우선인 경우 매우 효과적입니다!
⚙️ 프레임워크별 CSRF 방어 구현
대부분의 현대 웹 프레임워크는 CSRF 방어 기능을 기본으로 제공합니다!
Spring Security (Java) ☕
// 기본적으로 CSRF 보호 활성화됨
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// REST API의 경우 .csrf().disable() 가능
}
}
Django (Python) 🐍
# settings.py에서 CSRF 미들웨어 활성화 (기본값)
MIDDLEWARE = [
'django.middleware.csrf.CsrfViewMiddleware',
]
# 템플릿에서 사용
{% csrf_token %}
Express.js (Node.js) 🟢
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.post('/transfer', csrfProtection, (req, res) => {
// 토큰 검증 후 처리
});
Laravel (PHP) 🐘
<!-- Blade 템플릿에서 자동으로 토큰 삽입 -->
<form method="POST" action="/transfer">
@csrf
<!-- 폼 내용 -->
</form>
🆚 CSRF vs XSS: 헷갈리는 두 공격의 차이점
많은 분들이 CSRF와 XSS를 혼동하시는데, 핵심 차이는 이거예요:
구분 CSRF XSS
공격 대상 | 웹 애플리케이션이 사용자를 신뢰 | 사용자가 웹사이트를 신뢰 |
악성 코드 실행 위치 | 서버 측 | 클라이언트(브라우저) 측 |
공격 목적 | 사용자 권한으로 의도하지 않은 작업 수행 | 사용자 정보 탈취, 악성 스크립트 실행 |
쿠키 사용 여부 | 쿠키를 악용 | 쿠키를 탈취 |
간단히 말해, **CSRF는 '나를 사칭한 요청'이고, XSS는 '악성 스크립트 주입'**입니다!
🚀 REST API는 CSRF로부터 안전할까?
여기서 중요한 질문! "REST API를 사용하면 CSRF 공격을 걱정하지 않아도 될까요?"
답은 "상황에 따라 다릅니다" ⚠️
안전한 경우:
- JWT나 Bearer Token을 Authorization 헤더에 담아 사용하는 경우
- 쿠키를 전혀 사용하지 않는 경우
- localStorage나 sessionStorage에 토큰을 저장하고 JavaScript로 수동으로 헤더에 추가하는 경우
여전히 위험한 경우:
- REST API임에도 세션 쿠키를 사용하는 경우
- SameSite 설정이 None인 쿠키를 사용하는 경우
많은 개발자들이 "REST API니까 CSRF 보호를 비활성화해도 돼"라고 생각하지만, 쿠키를 사용한다면 여전히 위험합니다! 🚨
📱 2025년 트렌드: 모바일 앱도 조심해야!
최근에는 모바일 포렌식 도구를 이용한 CSRF 공격도 발견되고 있습니다.
2024년에는 Cellebrite라는 포렌식 회사가 개발한 도구로, 물리적 접근이 가능한 경우 Android 기기의 잠금을 해제하는 공격 체인이 발견되었죠. (CVE-2024-53104)
이처럼 CSRF의 공격 벡터는 계속 진화하고 있으며, 웹뿐만 아니라 모바일 환경에서도 보안에 신경 써야 합니다!
✅ 개발자를 위한 CSRF 방어 체크리스트
실무에서 바로 적용할 수 있는 체크리스트를 정리했어요! 📝
필수 사항:
- [ ] 쿠키 기반 인증을 사용한다면 CSRF 토큰 구현
- [ ] SameSite 쿠키 속성을 Lax 또는 Strict로 설정
- [ ] 중요한 작업은 POST/PUT/DELETE 메서드로만 처리
- [ ] 프레임워크의 기본 CSRF 보호 기능 활성화
권장 사항:
- [ ] Referer 헤더 검증 추가
- [ ] 민감한 작업에 재인증 또는 CAPTCHA 추가
- [ ] CORS 정책 올바르게 설정
- [ ] 정기적인 보안 감사 및 취약점 스캔
피해야 할 것:
- [ ] GET 요청으로 데이터 변경 작업 수행 금지
- [ ] REST API라고 무조건 CSRF 보호 비활성화 금지
- [ ] 프레임워크 기본 보안 설정 임의 변경 금지
💭 마무리하며
오늘은 CSRF 공격의 개념부터 실제 방어 방법까지 완벽하게 정리해봤습니다!
처음에는 복잡해 보일 수 있지만, 핵심은 간단해요:
- CSRF는 사용자 몰래 요청을 보내는 공격
- CSRF 토큰과 SameSite 쿠키로 효과적으로 방어 가능
- 프레임워크의 기본 보호 기능을 적극 활용하자
'기술의기록' 카테고리의 다른 글
Next.js 16.0 Beta 완전 분석 - 15버전과 뭐가 달라졌을까? (0) | 2025.10.13 |
---|---|
React 19.2 완벽 가이드 - 19.1과 비교하며 알아보는 최신 기능 (0) | 2025.10.10 |
다익스트라 알고리즘 완벽 정리 - 개념, 구현, 코딩테스트 활용법 (0) | 2025.10.02 |
2025년 최신! Claude Sonnet 4.5 vs Opus 4.1 완벽 비교 (0) | 2025.09.30 |
모바일 웹 개발자라면 꼭 알아야 할 CSS 새 뷰포트 단위 3가지 (dvh, svh, lvh 완벽 정리) (0) | 2025.09.30 |