본문으로 건너뛰기
김신건의 로그

[Auth] CSRF: 위조 요청 공격과 방어

· 수정 · 📖 약 3분 · 1,128자/단어 #csrf #security #auth #browser #backend
CSRF, Cross-Site Request Forgery, CSRF token, Synchronizer Token, Double Submit Cookie, SameSite cookie defense

정의

CSRF (Cross-Site Request Forgery)사용자가 로그인된 상태 에서 공격자가 만든 페이지피해자 브라우저로 위조 요청 을 보내는 공격. 쿠키 기반 인증의 큰 약점.

CSRF vs XSS

항목CSRFXSS
공격 경로브라우저 자동 쿠키 첨부 악용피해 사이트에 스크립트 주입
피해 사이트 코드 조작불필요필요
피해자 의도피해자가 요청을 모름피해자 브라우저에서 코드 실행
방어 포인트서버: 요청 출처 검증서버: 출력 인코딩
쿠키 의존쿠키 기반 세션에 한정어떤 인증 방식도 영향

공격 시나리오

sequenceDiagram
    autonumber
    participant V as Victim
    participant Bank as bank.com
    participant Evil as evil.com

    V->>Bank: 로그인
    Bank-->>V: Set-Cookie: session=ABC
    V->>Evil: 다른 탭에서 evil.com 방문
    Evil-->>V: HTML with <form action="bank.com/transfer">
    Note over V: HTML 안의 form 자동 submit (JS)
    V->>Bank: POST /transfer<br/>(자동 첨부 cookie: session=ABC)<br/>amount=1000&to=attacker
    Bank->>Bank: 인증된 세션 → 정상 처리
    Bank-->>V: 200 OK (이미 이체 됨)

핵심: 브라우저가 쿠키를 자동 첨부 한다는 점을 악용. XSS 와 다름. CSRF 는 공격자가 페이지에 코드 주입 못 해도 가능.

4가지 방어 패턴

flowchart TD
    Q{방어 패턴}
    Q --> A["1. SameSite Cookie<br/>(가장 단순)"]
    Q --> B["2. CSRF Token<br/>(Synchronizer)"]
    Q --> C["3. Double Submit Cookie"]
    Q --> D["4. Custom Header"]
Set-Cookie: SID=abc; HttpOnly; Secure; SameSite=Lax

SameSite=Lax 가 cross-site form POST 차단. 2020 부터 Chrome 의 기본값.

SameSite 값Cross-site GETCross-site POST설명
Strict차단차단완전 격리, 외부 링크 클릭도 차단
Laxtop-level 허용차단기본값, 대부분의 CSRF 차단
None허용허용Secure 필수, iframe / 3rd-party 용

IMPORTANT

SameSite=Lax 만으로도 대부분의 CSRF 차단. 옛 브라우저 호환 이 필요하면 추가 패턴.

2. CSRF Token (Synchronizer Token)

sequenceDiagram
    C->>Server: GET /form
    Server->>Server: token = random()
    Server-->>C: HTML + hidden input _csrf=token (session 에 token 저장)
    C->>Server: POST /transfer<br/>_csrf=token + body
    Server->>Server: session 의 token == 받은 token?
    Server-->>C: OK / 거절
  • 각 폼/요청마다 고유 token.
  • session 에 서버 측 저장 + 폼 hidden field.
  • Rails, Django, Spring 기본 적용.
GET /form
→ Set-Cookie: csrf_token=ABC
→ HTML: <input name="_csrf" value="ABC">

POST /transfer
→ Cookie: csrf_token=ABC
→ Body: _csrf=ABC

Server: cookie 의 csrf_token == body 의 _csrf ?
  • 서버 측 저장 없이 동작.
  • 단점: subdomain 의 cookie 침해 시 우회.

4. Custom Header (XHR/Fetch 한정)

fetch('/api/transfer', {
  method: 'POST',
  headers: {
    'X-Requested-With': 'XMLHttpRequest',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({...}),
});
  • Simple Request 가 아니므로 CORS preflight 발생.
  • 다른 origin 이면 preflight 거절 → 위조 요청 애초에 불가.
  • SPA + API깔끔한 패턴.

Origin / Referer 헤더 검증

서버에서 요청 출처를 직접 확인하는 보조 방어:

# 서버 측 Origin 검증 예시
def check_origin(request):
    origin = request.headers.get('Origin')
    referer = request.headers.get('Referer')
    allowed = {'https://myapp.com', 'https://www.myapp.com'}
    if origin:
        return origin in allowed
    if referer:
        return any(referer.startswith(o) for o in allowed)
    return False  # 둘 다 없으면 거절

WARNING

Referer 는 브라우저 설정 / 프록시로 제거될 수 있음. Origin + CSRF Token 을 같이 사용 권장. Referer 만으로는 불충분.

프레임워크 기본 내장

프레임워크CSRF 방어
DjangoCsrfViewMiddleware 기본 활성화 ({% csrf_token %} 태그)
Railsprotect_from_forgery :exception 기본
Spring Securitycsrf() 기본 활성화 (JWT 사용 시 disable)
LaravelVerifyCsrfToken 미들웨어 기본
Expresscsrf 패키지 별도 설치 필요

서버 렌더링 프레임워크는 기본 내장. SPA 는 별도 설정 필요.

CORS 와 CSRF 관계

CORS 와 CSRF 는 다른 문제. 혼동 주의.

flowchart LR
    CORS["CORS<br/>(Cross-Origin Resource Sharing)"]
    CSRF["CSRF<br/>(Cross-Site Request Forgery)"]

    CORS -->|"브라우저가 cross-origin 요청 허용 여부"| Browser["브라우저 정책"]
    CSRF -->|"인증된 사용자 대신 위조 요청"| Attack["공격 방어"]
항목CORSCSRF
목적다른 origin 의 JS 가 응답 읽기 허용위조 요청 자체를 차단
방어 대상응답 데이터 노출상태 변경 요청 위조
Simple Requestpreflight 없음 → CORS 우회 가능CSRF 공격 가능
관계CORS 허용해도 CSRF 방어 별도 필요CSRF 방어해도 CORS 설정 별도 필요

WARNING

CORS 를 허용했다고 CSRF 가 방어되는 것이 아님. Simple Request (form POST) 는 preflight 없이 전송되어 CORS 와 무관하게 CSRF 공격 가능.

어디에 어떤 방어?

환경권장
클래식 서버 렌더링 (Rails, Django)CSRF token + SameSite=Lax
SPA + REST APICustom header + SameSite=Strict
API + JWT in Authorization 헤더대부분 영향 없음 (cookie 안 씀)
모바일 앱 (자체 HTTP 라이브러리)영향 없음
3rd-party 통합 (iframe)SameSite=None + Secure + token

현대 브라우저 현황 (2024+)

flowchart TD
    Chrome["Chrome 80+<br/>SameSite=Lax 기본"]
    Firefox["Firefox 79+<br/>SameSite=Lax 기본"]
    Safari["Safari 13.1+<br/>SameSite 지원"]
    Old["IE / 구형 브라우저<br/>SameSite 미지원"]

    Chrome -->|"대부분 CSRF 자동 차단"| Safe["안전"]
    Firefox -->|"대부분 CSRF 자동 차단"| Safe
    Safari -->|"대부분 CSRF 자동 차단"| Safe
    Old -->|"추가 방어 필요"| Extra["CSRF Token 필수"]
브라우저SameSite=Lax 기본비고
Chrome 80+적용2020년 2월부터
Firefox 79+적용2020년 7월부터
Safari 13.1+적용2020년 3월부터
IE 11미지원레거시 환경은 CSRF Token 필수

현대 브라우저 환경에서는 SameSite=Lax 기본값으로 대부분 방어. 레거시 지원 필요 시 CSRF Token 병행.

GET 의 위험성

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

CAUTION

상태 변경 GETCSRF 의 가장 흔한 함정. 모든 변경 요청은 POST/PUT/DELETE 만. GET 은 idempotent + read-only.

토큰 구현 시 주의사항

CSRF Token 을 직접 구현할 때:

import secrets
import hmac
import hashlib

def generate_csrf_token(session_id: str, secret: str) -> str:
    # 세션 ID 와 서버 secret 으로 HMAC 생성
    # 단순 random 보다 세션 바인딩이 더 안전
    random_part = secrets.token_hex(16)
    mac = hmac.new(
        secret.encode(),
        f"{session_id}:{random_part}".encode(),
        hashlib.sha256,
    ).hexdigest()
    return f"{random_part}:{mac}"

단순 random() 토큰 은 서버 재시작 시 세션과 분리될 수 있음. HMAC 기반 토큰 이 더 안전하고 stateless 구현 가능.

흔한 함정

WARNING

  1. SameSite=None 인데 Secure 없음 = 브라우저가 거절. 옛 코드 깨짐.
  2. CSRF token 을 모든 요청에서 동일 = 하나만 탈취 되면 모든 요청 위조. 세션 단위 회전.
  3. GET 의 상태 변경 = SameSite=Lax 가 top-level GET 허용 → CSRF.
  4. Multipart form 의 Content-Type = simple request 라 preflight 없음. Custom header 방어가 부분적.
  5. JWT + cookie 혼합 = Authorization 헤더 JWT 는 안전하지만 cookie 에 저장된 JWT 는 CSRF 위험.

관련 위키

이 글의 용어 (4개)
[Auth] JWT: 구조, 서명, 만료, refresh tokenauth-security
정의 JWT (JSON Web Token) (RFC 7519) 은 base64url 인코딩된 JSON + 서명 형태의 self-contained token. 서버가 세션 저장 없…
[Auth] Session Cookie: HttpOnly, SameSite, Secure flagauth-security
정의 Session Cookie 는 서버가 발급한 임의 ID 를 쿠키에 보관. 매 요청 자동 첨부. 서버 측 세션 store (Redis, DB) 와 짝. [!IMPORTANT]…
[Django] 보안: CSRF, XSS, SQL injection, headersdjango
정의 Django는 OWASP Top 10 대부분을 기본 방어한다: CSRF 토큰, ORM의 SQL 인젝션 방지, 템플릿 자동 escape, 비밀번호 해싱, 세션 쿠키 보안 등.…
[Network] CORS: Same-Origin Policy, preflight, credentialsnetwork
정의 CORS (Cross-Origin Resource Sharing) 는 Same-Origin Policy (SOP) 의 완화 메커니즘. 브라우저가 origin 이 다른 리소스…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기