HiDevelop

CSRF 공격에 대하여.. 본문

카테고리 없음

CSRF 공격에 대하여..

꽃달린감나무 2026. 1. 27. 13:46

근래 개발을 하다가, CSRF 공격에 대해 공부할 기회가 생겨, 공부한 것을 토대로 내용을 공유하고자 한다

Cookie의 동작 원리

브라우저는 서버로 요청을 보낼 때, 요청 목적지 도메인에 해당하는 쿠키를 자동으로 포함해서 전송한다.
이 과정은 개발자가 직접 제어하지 않아도 브라우저 레벨에서 자동으로 처리된다.

중요한 점은 다음이다.

  • 사용자가 직접 버튼을 눌렀는지
  • 다른 사이트에서 요청이 시작됐는지

이 여부와 무관하게,
요청 URL의 도메인이 쿠키 도메인과 일치하면 쿠키는 자동으로 전송된다.


Cookie의 특징

1. 같은 도메인이면 쿠키를 공유한다

예를 들어 bank.com에서 로그인하면 인증 정보가 쿠키에 저장된다.

이 상태에서 사용자가 악성 사이트에 접속했다고 가정해보자.
그 사이트 내부에 다음과 같은 요청이 숨어 있다면:

<form action="https://bank.com/request" method="POST">

브라우저는 요청의 출처가 어디든 상관하지 않고
요청 목적지가 bank.com이라는 이유만으로 기존 쿠키를 자동 포함시킨다.

이 특성이 바로 CSRF 공격의 출발점이다.


CSRF (Cross-Site Request Forgery)

CSRF는 브라우저의 쿠키 자동 전송 특성을 악용한 공격 기법이다.

브라우저는 요청을 보낼 때 다음 기준만 확인한다.

  • 요청 URL의 도메인
  • 쿠키의 도메인 / 경로 / 정책

반대로, 다음은 확인하지 않는다.

  • 요청이 어떤 사이트에서 시작됐는지
  • 사용자가 직접 의도한 요청인지

즉, 서버 입장에서는
쿠키만 보고는 이 요청이 정상 요청인지, 공격 요청인지 판단할 수 없다.

이 점이 CSRF가 가능한 근본 원인이다.


CSRF 방어 방법

1. CSRF Token 방식 (기본이자 정공법)

핵심 아이디어는 단순하다.

이 요청이 사용자가 직접 만든 요청임을 서버가 증명받자

동작 흐름

  1. 서버가 랜덤 토큰을 생성
  2. 클라이언트가 요청 시 토큰을 함께 전송
  3. 서버가 기존 값과 비교
// 1. 서버에서 토큰 발급
String csrfToken = generateRandomToken();
session.setAttribute("CSRF_TOKEN", csrfToken);

// 2. 요청 시 비교
String sessionToken = session.getAttribute("CSRF_TOKEN");
String requestToken = request.getParameter("_csrf");

if (!sessionToken.equals(requestToken)) {
    throw new CsrfException();
}

외부 사이트에서는 이 토큰 값을 알 수 없기 때문에
요청을 만들어도 서버 검증 단계에서 차단된다.

현재도 가장 표준적인 CSRF 방어 방식이다.


2. SameSite Cookie (보조 수단)

SameSite는
외부 사이트에서 시작된 요청에 쿠키를 보낼지 말지를 브라우저에 알려주는 옵션이다.

Set-Cookie: JSESSIONID=abc123; SameSite=Strict

옵션 종류

Strict

  • 외부 사이트에서 시작된 요청 → 쿠키 전송 안 됨
  • 주소창 직접 입력 → 쿠키 전송 안 됨

장점 :CSRF 방어 효과가 매우 큼
단점 : OAuth, 외부 로그인 흐름이 거의 불가능


Lax ( 기본값)

  • 외부 GET 링크 클릭 → 쿠키 전송
  • 외부 POST / form 요청 → 쿠키 차단
  • 내부 요청 → 쿠키 전송

대부분의 서비스에서 기본값으로 사용된다.


None

  • 외부 요청 전부 허용
  • HTTPS 필수

OAuth, SSO 같은 외부 인증이 필요한 경우 사용된다.


SameSite의 한계

SameSite는 브라우저 정책에 의존한다.
서버가 요청의 의도를 직접 검증하지는 못한다.

따라서 CSRF Token을 대체할 수 없으며,
반드시 보조 수단으로만 사용해야 한다.


3. Origin / Referer 검증 (보조 수단)

요청 헤더에 포함된 출처 정보를 검사하는 방식이다.

이 요청이 우리 서비스에서 시작된 요청인지 확인한다

Origin 헤더

Origin: https://myapp.com

  • 주로 POST / PUT / DELETE 요청에 포함
  • 출처 도메인만 전달

Referer 헤더

Referer: https://myapp.com/page/form

  • 요청이 발생한 정확한 URL
  • GET 요청에도 자주 포함
  • 브라우저 보안 정책에 따라 제거될 수 있음

CSRF 공격에서 왜 효과적인가

  • 정상 요청
    Origin: https://bank.com
  • 공격 요청
    Origin: https://evil.com

서버에서는 다음과 같이 검증할 수 있다.

if (!origin.equals("https://bank.com")) {     
    reject(); 
}

한계점

  • 헤더가 누락될 수 있음
  • XSS 환경에서는 무력화
  • 브라우저 정책에 의존

따라서 단독 사용은 위험하며,
CSRF Token의 보조 수단으로만 사용하는 것이 적절하다.


실제로는 어떻게 적용할까?

  1. Spring Security 사용
    • CSRF Token 방식이 기본 적용됨
    • 대부분 이 설정으로 충분
  2. Spring Security 미사용
    • 직접 구현 필요
    • Filter에서 처리하는 것이 일반적

왜 Filter에서 CSRF 검증을 할까?

OncePerRequestFilter를 사용하는 이유

CSRF 검증은 요청당 한 번이면 충분하다.

일반 Filter를 사용하면 :

  • FORWARD / ERROR 디스패치마다 다시 실행됨
  • 불필요한 비용 발생

OncePerRequestFilter는 요청당 한 번만 실행되므로 적합하다.


Interceptor나 Controller를 피하는 이유

  • CSRF는 비즈니스 로직이 아니라 요청 검증 영역
  • Filter는 Spring MVC 진입 전에 차단 가능
  • 불필요한 리소스 사용을 줄일 수 있음

정리

CSRF는 브라우저의 쿠키 자동 전송 특성 때문에 발생한다.
가장 안전한 구조는 다음과 같다.

  • 기본: CSRF Token
  • 보조: SameSite Cookie, Origin / Referer 검증

이 조합이 현재 웹 환경에서 가장 현실적이고 안정적인 선택이다.

728x90