인프런/스프링 시큐리티 완전 정복 [6.x 개정판]

17) 악용보호 - CSRF(Cross Site Request Forgery, 사이트 간 요청 위조)

backend dev 2024. 10. 28.

CSRF(Cross Site Request Forgery, 사이트 간 요청 위조)

 

• 웹 애플리케이션의 보안 취약점으로 공격자가 사용자로 하여금 이미 인증된 다른 사이트에 대해 원치 않는 작업을 수행하게 만드는 기법을 말한다

 

• 이 공격은 사용자의 브라우저가 자동으로 보낼 수 있는 인증 정보, 예를 들어 쿠키나 기본 인증 세션을 이용하여 사용자가 의도하지 않은 요청을 서버로 전송하게 만든다

 

• 이는 사용자가 로그인한 상태에서 악의적인 웹사이트를 방문하거나 이메일 등을 통해 악의적인 링크를 클릭할 때 발생할 수 있다

 

 

 

CSRF 진행 순서

 

CSRF 기능 활성화

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
 http.csrf(Customizer.withDefaults()); // csrf 의 기능을 활성화 한다. 별도 설정하지 않아도 활성화 상태로 초기화 된다
 return http.build();
}

 

 

토큰은 서버에 의해 생성되어 클라이언트의 세션에 저장되고 폼을 통해 서버로 전송되는 모든 변경 요청에 포함되어야 하며 서버는 이 토큰을 검증하여 요청의 유효성을 확인한다

 

[csrf 토큰은 서버에 의해 생성되는 랜덤한 문자열 토큰이다. ]

 

기본 설정은 'GET', 'HEAD', 'TRACE', 'OPTIONS’ 와 같은 안전한 메서드를 무시하고 'POST', 'PUT', 'DELETE’ 와 같은 변경 요청 메서드에 대해서만 CSRF 토큰 검사를 수행한다

 

 

중요한 점은 실제 CSRF 토큰이 브라우저에 의해 자동으로 포함되지 않는 요청 부분에 위치해야 한다는 것으로서

HTTP 매개변수나 헤더에 실제 CSRF 토큰 을 요구하는 것이 CSRF 공격을 방지하는데 효과적이라 할 수 있다


 

  • CSRF 공격 방지: CSRF 공격은 사용자가 원하지 않는 요청이 외부 사이트에 의해 자동으로 실행되는 상황을 의미합니다. 예를 들어, 사용자가 로그인한 상태에서 악성 사이트가 사용자의 계정 정보를 이용해 요청을 보내는 것이죠.
  • CSRF 토큰의 필요성: CSRF 공격을 막기 위해 CSRF 토큰을 사용합니다. CSRF 토큰은 서버가 생성하고 클라이언트(브라우저)에 전달하는 임의의 값입니다. 이후 클라이언트가 서버에 요청을 보낼 때 이 CSRF 토큰을 함께 전달해야 서버가 해당 요청이 정상적인 요청인지 확인할 수 있습니다.

 

"CSRF 토큰이 자동으로 포함되지 않는 형태로 요청해야 한다"는 뜻은, 브라우저가 기본적으로 보내는 요청에 자동으로 CSRF 토큰이 포함되지 않도록 하고, 개발자가 명시적으로 CSRF 토큰을 추가해야 한다는 의미입니다. CSRF 토큰은 주로 HTTP 헤더나 요청 본문에 개발자가 수동으로 넣어야 하며, 이것이 CSRF 공격을 막는 데 중요합니다. 조금 더 구체적으로 설명해볼게요.

왜 자동으로 포함되지 않는 형태가 중요한가?

CSRF 공격은 악성 웹사이트가 사용자의 인증 상태를 이용해 원치 않는 요청을 서버로 보내는 것이기 때문에, 브라우저가 모든 요청에 자동으로 CSRF 토큰을 포함하게 되면 보안이 약해질 수 있습니다. 예를 들어, 다음과 같은 상황이 있습니다:

  1. 자동으로 포함될 때의 문제점: 만약 CSRF 토큰이 브라우저에 의해 자동으로 요청에 포함되도록 설정된다면, 공격자가 악성 스크립트를 통해 사용자의 브라우저로 요청을 전송할 때에도 CSRF 토큰이 포함되어 보안이 무력화될 수 있습니다.
  2. 개발자가 명시적으로 CSRF 토큰을 추가하는 방식의 장점: 반대로, CSRF 토큰이 자동으로 포함되지 않게 하고 개발자가 특정 방식으로 수동으로 추가하도록 한다면, CSRF 공격에 의한 악성 요청은 CSRF 토큰이 누락되어 서버에서 차단할 수 있습니다.

예를 들어, 웹 애플리케이션에서는 주로 JavaScript를 통해 CSRF 토큰을 읽고, 이를 요청 헤더나 본문에 수동으로 추가하는 방식으로 보호를 강화합니다.

요약

CSRF 토큰을 "자동으로 포함되지 않게 설정"한다는 것은, 브라우저가 단순히 쿠키처럼 모든 요청에 CSRF 토큰을 자동으로 추가하는 방식이 아니라, 개발자가 CSRF 토큰을 요청의 특정 위치에 명시적으로 포함시키는 방식으로 설계해야 한다는 의미입니다.


 

반면에 쿠키에 토큰을 요구하는 것은 브라우저가 쿠키를 자동으로 요청에 포함시키기 때문에

효과적이지 않다고 볼 수 있다

 


CSRF 토큰을 쿠키에 저장하는 방식이 CSRF 공격 방지에 효과적이지 않다는 의미입니다. 그 이유는 브라우저가 쿠키를 자동으로 모든 요청에 포함시키기 때문인데, 이를 좀 더 구체적으로 설명해 볼게요.

왜 쿠키에 CSRF 토큰을 저장하면 안 될까?

  1. 쿠키의 자동 전송 특성: 브라우저는 동일한 도메인으로 요청을 보낼 때마다 자동으로 해당 도메인의 쿠키를 요청에 포함시킵니다. 즉, 사용자가 브라우저에서 사이트에 요청을 보내면, CSRF 토큰이 쿠키에 저장되어 있는 경우 브라우저가 이를 자동으로 포함해 보내게 됩니다.
  2. CSRF 공격의 특징: CSRF 공격은 공격자가 사용자를 특정 웹사이트에 접속하게 만든 다음, 그 사용자의 세션 쿠키 등을 이용해 무단 요청을 발생시키는 공격입니다. 만약 CSRF 토큰이 쿠키에 저장되어 있다면, 악성 사이트에서 보낸 요청에도 쿠키가 자동으로 포함되므로, 서버는 이를 정상적인 요청으로 처리할 수 있습니다.
  3. 헤더나 본문에 포함할 때의 장점: CSRF 토큰을 HTTP 헤더나 요청 본문에 포함하면, 악성 사이트는 JavaScript를 통해 해당 CSRF 토큰을 가져와서 요청에 포함시키기가 어렵습니다. 따라서 CSRF 토큰이 제대로 포함되지 않은 악성 요청은 서버에서 차단할 수 있습니다.

요약

즉, CSRF 토큰을 쿠키에 저장할 경우 브라우저가 자동으로 모든 요청에 토큰을 포함시키게 되므로 CSRF 공격에 취약해집니다. 대신, 개발자가 CSRF 토큰을 수동으로 헤더나 본문에 포함하도록 하는 방식이 더 안전합니다.

 

악성 사이트가 JavaScript를 통해 CSRF 토큰을 가져오기 어려운 이유는 웹 브라우저의 보안 정책인 "동일 출처 정책(Same-Origin Policy)" 때문입니다. 이 정책이 어떻게 동작하는지 설명할게요.

동일 출처 정책(Same-Origin Policy)이란?

동일 출처 정책은 특정 도메인(출처)에서 로드된 웹 페이지의 스크립트가 다른 도메인(출처)에 있는 리소스에 접근하는 것을 제한하는 보안 기능입니다. 이를 통해 악성 사이트가 사용자의 정보에 무단으로 접근하지 못하도록 합니다.

  1. 출처(Origin): 출처는 프로토콜(https://), 호스트명(예: example.com), **포트(예: 443)**의 조합으로 정의됩니다. 두 개의 URL이 동일한 프로토콜, 호스트명, 포트를 가지면 동일 출처라고 봅니다.
  2. 정책 적용 예시:

CSRF 토큰을 헤더나 본문에 포함할 때의 장점

CSRF 토큰을 요청의 헤더나 본문에 넣어야 서버가 요청을 받아들일 때 유효성을 확인할 수 있습니다. 그러나 이 토큰은 동일 출처 정책 덕분에 악성 사이트가 JavaScript로 읽어올 수 없습니다. 따라서 악성 사이트는 CSRF 토큰이 없는 요청만을 보낼 수 있게 되므로, 서버에서 해당 요청을 거부할 수 있습니다.

요약

악성 사이트가 CSRF 토큰을 가져오기 어려운 이유는 동일 출처 정책 때문에 사용자가 로그인한 도메인의 데이터에 접근할 수 없기 때문입니다. 이를 통해 CSRF 토큰을 안전하게 보호하고, 악성 요청이 서버에서 차단되도록 할 수 있습니다.

 

동일 출처 정책(Same-Origin Policy)은 요청 방식(GET, POST 등)에 상관없이 JavaScript가 다른 출처의 응답 데이터를 읽는 것을 제한하는 정책입니다. 이 정책 때문에 악성 사이트의 스크립트가 trusted-site.com의 응답 내용을 읽지 못하지만, 요청 자체는 GET이든 POST든 성공할 수 있습니다.

요청과 동일 출처 정책의 동작 방식

  1. 요청 자체는 제한되지 않음: 동일 출처 정책은 악성 사이트에서 trusted-site.com으로 보내는 GET이나 POST 요청을 차단하지 않습니다. 즉, 악성 사이트는 사용자의 인증 상태(쿠키)를 이용해 trusted-site.com에 POST 요청을 보내는 것이 가능합니다.
  2. 응답 데이터 읽기 제한: 동일 출처 정책은 악성 사이트의 JavaScript가 다른 출처에서 응답한 데이터를 읽는 것만 제한합니다. 따라서 악성 사이트에서 POST 요청을 보내는 것 자체는 가능하지만, 응답 데이터는 동일 출처가 아니므로 읽을 수 없습니다.

CSRF 공격 시의 동작 예시

  • 예를 들어, 사용자가 trusted-site.com에 로그인한 상태에서 malicious-site.com을 방문했다고 가정해 봅시다.
  • malicious-site.com에서는 trusted-site.com에 POST 요청을 보낼 수 있고, 사용자의 브라우저는 로그인 세션 쿠키를 자동으로 포함해 이 요청을 서버로 전송합니다.
  • 서버는 세션 쿠키 덕분에 요청이 유효한 사용자로부터 온 것이라고 판단해 처리할 수 있지만, 동일 출처 정책 때문에 악성 사이트는 응답 데이터를 읽지 못합니다.

CSRF 방지를 위한 CSRF 토큰 사용 이유

동일 출처 정책만으로는 CSRF 공격을 막을 수 없습니다. 그래서 CSRF 방어용 토큰을 사용해, 요청을 보낼 때 유효한 CSRF 토큰이 함께 포함되어 있는지 확인합니다. 이렇게 하면 악성 사이트가 CSRF 토큰을 알 수 없기 때문에 토큰을 포함한 유효한 요청을 생성할 수 없고, 서버는 CSRF 토큰이 없는 요청을 차단하게 됩니다.

결론

동일 출처 정책은 요청 방식에 상관없이 다른 출처의 응답 데이터를 읽는 것을 막지만, 요청 자체를 차단하지 않기 때문에 CSRF 공격을 완전히 방지하지는 않습니다.

 

 

 

동일 출처 정책(Same-Origin Policy)과 CORS(Cross-Origin Resource Sharing)의 관계를 설명드리겠습니다:

  1. 동일 출처 정책
  • 브라우저의 기본적인 보안 정책
  • 다른 출처의 리소스와 상호작용을 제한하는 "제한적인 정책"
  • 기본적으로 차단이 디폴트입니다
// example.com에서 실행
fetch('https://api.different-site.com/data') 
  // -> 기본적으로 차단됨
 
  1. CORS
  • 동일 출처 정책을 우회할 수 있게 해주는 "허용 메커니즘"
  • 서버가 "이 출처는 괜찮아요"라고 브라우저에게 알려주는 방식
  • HTTP 헤더를 통해 접근 권한을 부여합니다

예시:

// 서버 응답 헤더 
Access-Control-Allow-Origin: https://example.com 
Access-Control-Allow-Methods: GET, POST 
Access-Control-Allow-Headers: Content-Type

 

정리하면:

  • 동일 출처 정책: "다른 출처는 기본적으로 금지!"
  • CORS: "이 출처는 예외적으로 허용해줘!"

비유하자면:

  • 동일 출처 정책 = 건물의 보안 정책 ("외부인 출입금지")
  • CORS = 방문증 발급 시스템 ("이 사람은 허가했으니 들여보내줘")

따라서 CORS는 동일 출처 정책을 대체하는 것이 아니라, 안전하게 우회할 수 있는 방법을 제공하는 보완 메커니즘입니다.


CSRF 기능 비활성화

 

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http.csrf(csrf -> csrf.disabled()
    return http.build();
}

csrf 의 기능 전체 비활성화

 

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    http.csrf(csrf -> csrf.ignoringRequestMatchers("/api/*")); // 한다
    return http.build();
}

csrf 보호가 필요하지 않은 특정 엔드포인트만 비활성화


CSRF(Cross-Site Request Forgery) 공격은 주로 사용자가 인증된 세션이나 쿠키를 가지고 있을 때 발생하는 공격입니다. 사용자가 로그인한 상태에서 악성 웹사이트가 해당 사용자의 세션을 이용해 의도하지 않은 요청을 서버에 보내는 방식입니다. 따라서 쿠키나 세션을 사용하지 않는 환경에서는 CSRF를 비활성화해야 하는 이유를 살펴보겠습니다.

CSRF 비활성화 이유

  1. 인증 상태가 없음:
    • CSRF 공격은 인증된 사용자가 세션을 통해 요청을 보내는 경우에 발생합니다. 쿠키나 세션을 사용하지 않는 경우, 사용자가 인증되지 않았거나 상태가 없으므로 CSRF 공격의 위험이 존재하지 않습니다.
    • 예를 들어, API가 사용자 인증 없이 접근 가능하다면, CSRF 공격은 의미가 없게 됩니다. 공격자가 어떤 요청을 보내더라도 사용자가 로그인 상태가 아니기 때문에 해당 요청이 유효하지 않게 됩니다.
  2. 세션 관리 필요 없음:
    • CSRF는 세션 관리와 깊은 연관이 있습니다. 세션이 없으면 서버는 요청의 출처를 확인할 필요가 없고, CSRF 토큰을 사용할 이유가 없습니다.
    • 예를 들어, RESTful API에서 인증 없이 사용되는 GET 요청이나 POST 요청은 CSRF 공격을 받을 수 있는 환경이 아닙니다.
  3. 추가적인 복잡성 제거:
    • CSRF 방어를 위한 토큰 관리 및 검증 로직이 필요 없으므로, 코드가 간단해지고 유지 관리가 용이해집니다.
    • API가 비공식적인 사용이나 공개된 데이터 조회를 위해 설계된 경우, CSRF 보호 로직이 없더라도 서비스의 기능에는 영향을 미치지 않습니다.

CSRF 보호가 필요한 이유가 바로 쿠키/세션 기반 인증 때문입니다. 이해를 돕기 위해 설명드리겠습니다:

1. CSRF 공격이 위험한 이유:

// 사용자가 bank.com에 로그인된 상태
// evil.com이 bank.com으로 돈 보내기 요청을 보냄
fetch('https://bank.com/transfer', {
  credentials: 'include'  // 쿠키가 자동으로 포함됨!
})



2. JWT를 헤더에 사용하는 경우:

// Authorization 헤더를 수동으로 넣어야 함
fetch('https://api.com/data', {
  headers: {
    'Authorization': 'Bearer jwt토큰...'
  }
})



차이점:
- 쿠키/세션: 브라우저가 자동으로 인증정보 전송
- JWT in Header: 개발자가 수동으로 넣어야 함

CSRF가 필요없는 이유:
1. evil.com에서는 bank.com의 JWT 토큰을 알 수 없음
2. 알더라도 헤더에 수동으로 넣어야 하는데, 이는 동일출처정책에 의해 차단됨
3. 따라서 CSRF 공격 자체가 불가능

정리:
- 쿠키/세션 사용시: CSRF 보호 필요 (자동전송 때문)
- 토큰 기반 인증: CSRF 보호 불필요 (수동전송이라서)

비유하면:
- 쿠키/세션 = 자동문이라 아무나 들어올 수 있음 → 보안요원(CSRF) 필요
- 토큰 인증 = 수동문이라 직접 열어야만 함 → 보안요원 불필요


따로 csrf 설정이 없더라도 Spring Seuciry는 csrf토큰을 사용하도록 설정이 되어있다. 

permitAll인 url에 요청하더라도 csrf토큰이 없이 요청을 하게되면 

로그인사이트가 나오는데 거기 hidden input으로 csrf가 있는것을 확인할 수 있다.

 

csrf를 disable하면 요청이 성공적으로 진행된다.

 

 

CSRF 토큰 유지 및 검증

 

CSRF 토큰 유지­ - CsrfTokenRepository

CsrfTokenCsrfTokenRepository를 사용하여 영속화 하며 HttpSessionCsrfTokenRepositoryCookieCsrfTokenRepository 를 지원한다

 

두 군데 중 원하는 위치에 토큰을 저장하도록 설정을 통해 지정할 수 있다

 

 

1. 세션에 토큰 저장 - HttpSessionCsrfTokenRepository

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
    http.csrf(csrf -> csrf.csrfTokenRepository(repository));
    return http.build();
}

기본적으로 토큰을 세션에 저장하기 위해 HttpSessionCsrfTokenRepository를 사용한다

위와같은 설정을 하지않아도 기본적으로 적용되어있다.

 

HttpSessionCsrfTokenRepository기본적으로 HTTP 요청 헤더인 X-CSRF-TOKEN

또는 요청 매개변수인 _csrf에서 토큰을 읽는다

 

기본설정으로 다음과 같은 이름들이 필드로 들어가있다.

 

2. 쿠키에 토큰 저장 - CookieCsrfTokenRepository

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    CookieCsrfTokenRepository repository = new CookieCsrfTokenRepository();
    http.csrf(csrf -> csrf.csrfTokenRepository(repository)); // 1번방법
    http.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())); // 2번방법 - 자바스크립트에서 쿠키를 읽어오고싶다면 다음과 같이한다.
    return http.build();
}

 

JavaScript 기반 애플리케이션을 지원하기 위해 CsrfToken을 쿠키에 유지할 수 있으며

구현체로 CookieCsrfTokenRepository를 사용할 수 있다

 

CookieCsrfTokenRepository 는 기본적으로 XSRF-TOKEN 명을 가진 쿠키에 작성하고

HTTP 요청 헤더인 X-XSRF-TOKEN 또는 요청 매개변수인 _csrf에서 읽는다

JavaScript 에서 쿠키를 읽을 수 있도록 HttpOnly를 명시적으로 false로 설정할 수 있다 

 

JavaScript로 직접 쿠키를 읽을 필요가 없는 경우 보안을 개선하기 위해 HttpOnly 를 생략하는 것이 좋다

 


CSRF(Cross-Site Request Forgery) 토큰을 세션에 저장하는 것과 쿠키에 저장하는 것은 각각의 저장 방식에 따라 보안과 관리의 차이가 있습니다. 다음은 두 가지 방식의 주요 차이점입니다:

1. 저장 위치

  • 세션에 저장:
    • CSRF 토큰은 서버 측 세션에 저장됩니다. 사용자가 로그인할 때 생성된 세션에 CSRF 토큰이 함께 저장되고, 이 토큰은 해당 세션에만 유효합니다.
    • 서버는 세션 ID를 통해 특정 사용자와 연결된 CSRF 토큰을 확인할 수 있습니다.
  • 쿠키에 저장:
    • CSRF 토큰은 클라이언트의 브라우저에 쿠키로 저장됩니다. 이 쿠키는 특정 도메인과 경로에 대해 자동으로 포함됩니다.
    • 일반적으로 CSRF 토큰을 저장하는 쿠키는 SameSite 속성을 설정하여 다른 출처에서의 요청을 제한하는 방식으로 보안을 강화할 수 있습니다.

2. 보안성

  • 세션에 저장:
    • CSRF 토큰을 세션에 저장하면, 해당 토큰이 세션과 연결되어 있으므로 각 사용자에게 고유합니다. 이 경우, 악의적인 사이트가 CSRF 토큰을 가져갈 가능성이 줄어듭니다.
    • 서버가 CSRF 토큰을 검증할 때, 사용자의 세션에 저장된 토큰과 비교하여 요청의 유효성을 확인하므로 안전합니다.
  • 쿠키에 저장:
    • CSRF 토큰이 쿠키에 저장되면, 브라우저가 자동으로 요청 시 해당 쿠키를 포함시킵니다. 이는 사용자가 악의적인 사이트에 방문할 경우 CSRF 공격에 노출될 수 있는 위험을 내포합니다.
    • 악의적인 사이트가 JavaScript를 통해 해당 쿠키를 읽어들일 수 있는 경우, CSRF 공격이 성공할 수 있습니다.

3. 토큰 관리

  • 세션에 저장:
    • 세션에서 CSRF 토큰을 관리하면 서버 측에서 직접 관리할 수 있으므로, 보안 및 관리를 중앙집중화할 수 있습니다.
    • 사용자가 로그아웃하거나 세션이 만료되면 CSRF 토큰이 자동으로 무효화됩니다.
  • 쿠키에 저장:
    • 쿠키로 CSRF 토큰을 관리하는 경우, 클라이언트 측에서 관리되므로 서버의 직접적인 통제를 받지 않습니다.
    • 쿠키에 저장된 CSRF 토큰은 사용자가 로그아웃하더라도 클라이언트의 쿠키에 남아있을 수 있으며, 이를 통해 공격자가 악용할 수 있는 여지가 있습니다.

요약

  • CSRF 토큰을 세션에 저장하는 것은 보안성과 관리 측면에서 우수합니다. 사용자의 세션과 연결되어 있어, 악의적인 사이트가 토큰을 가져가거나 사용하는 것이 어렵습니다.
  • CSRF 토큰을 쿠키에 저장하는 것은 상대적으로 보안에 취약하며, 악의적인 요청이 발생할 수 있는 가능성이 높아집니다. 따라서 CSRF 보호를 위한 가장 안전한 방법은 CSRF 토큰을 서버의 세션에 저장하고 이를 통해 관리하는 것입니다.

CSRF 토큰 처리­ - CsrfTokenRequestHandler

CsrfTokenCsrfTokenRequestHandler 를 사용하여 토큰을 생성 및 응답하고

HTTP 헤더 또는 요청 매개변수로부터 토큰의 유효성을 검증하도록 한다

 

XorCsrfTokenRequestAttributeHandlerCsrfTokenRequestAttributeHandler 를 제공하며

사용자 정의 핸들러를 구현할 수 있다

 

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    XorCsrfTokenRequestAttributeHandler csrfTokenHandler = new XorCsrfTokenRequestAttributeHandler();
    http.csrf(csrf -> csrf.csrfTokenRequestHandler(csrfTokenHandler));
    return http.build();
}

 

_csrf  및 CsrfToken.class.getName() 명으로

HttpServletRequest 속성에 CsrfToken 을 저장하며

HttpServletRequest 으로부터 CsrfToken 을 꺼내어 참조할 수 있다

 

토큰 값을 요청 헤더 (기본적으로 X-CSRF-TOKEN 또는 X-XSRF-TOKEN 중 하나)

또는 요청 매개변수 (_csrf) 중 하나로부터 토큰의 유효성 비교 및 검증을 해결한다

 

클라이언트의 매 요청마다 CSRF 토큰 값(UUID) 에 난수를 인코딩하여 변경한 CsrfToken이 반환 되도록 보장한다.

세션에 저장된 원본 토큰 값은 그대로 유지한다

 

헤더 값 또는 요청 매개변수로 전달된 인코딩 된 토큰은 원본 토큰을 얻기 위해 디코딩되며, 그런 다음 세션 혹은 쿠키에 저장된 영구적인 CsrfToken과 비교된다

 

[CSRF토큰 원본은 세션혹은 쿠키에 저장하고, 매 요청마다 난수로 인코딩하여 반환한다.

요청올떄 난수로 인코딩된 CSRF토큰을 디코딩해서 원본과 비교하는 작업을 한다.]

 

 

CSRF 토큰 지연 로딩

 

기본적으로 Spring Security 는 CsrfToken 을 필요할 때까지 로딩을 지연시키는 전략을 사용한다.

그러므로 CsrfToken 은 HttpSession 에 저장되어 있기 때문에

매 요청마다 세션으로부터 CsrfToken 을 로드할 필요가 없어져 성능을 향상시킬 수 있다

 

CsrfTokenPOST 와 같은 안전하지 않은 HTTP 메서드를 사용하여 요청이 발생할 때와

CSRF 토큰을 응답에 렌더링하는 모든 요청에서 필요하기 때문에

그 외 요청에는 지연로딩 하는 것이 권장된다

 

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    XorCsrfTokenRequestAttributeHandler handler = new XorCsrfTokenRequestAttributeHandler();
    handler.setCsrfRequestAttributeName(null); //지연된 토큰을 사용하지 않고 CsrfToken 을 모든 요청마다 로드한다
    http.csrf(csrf -> csrf
            .csrfTokenRequestHandler(handler));
    return http.build();
}

 

 

CSRF 통합

CSRF 공격을 방지하기 위한 토큰 패턴을 사용하려면 실제 CSRF 토큰을 HTTP 요청에 포함해야 한다

 

그래서 브라우저에 의해 HTTP 요청에 자동으로 포함되지 않는

요청 부분(폼 매개변수, HTTP 헤더 또는 기타 부분) 중 하나에 포함되어야 한다

 

클라이언트 어플리케이션이 CSRF로 보호된 백엔드 애플리케이션과 통합하는 여러 가지 방법을 살펴보자

 

 

HTML Forms

HTML 폼을 서버에 제출하려면 CSRF 토큰을 hidden 값으로 Form 에 포함해야 한다

<form action="/memberJoin" method="post">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
<input type="hidden" name="_csrf" value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>

 

폼에 실제 CSRF 토큰을 자동으로 포함하는 뷰는 다음과 같다

 

- Thymeleaf

 

- Spring 의 폼 태그 라이브러리 

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

 

 

JavaScript Applications

Single Page Application [ SPA ]

CSRF 토큰을 클라이언트와 서버 간에 주고받으면서 이를 검증하는 과정

 

CookieCsrfTokenRepository.withHttpOnlyFalse 를 사용해서

클라이언트가 서버가 발행한 쿠키로 부터 CSRF 토큰을 읽을 수 있도록 한다

 

사용자 정의 CsrfTokenRequestHandler을 만들어 클라이언트가 요청 헤더나 요청 파라미터로 CSRF 토큰을

제출할 경우 이를 검증하도록 구현한다

 

클라이언트의 요청에 대해 CSRF 토큰을 쿠키에 렌더링해서 응답할 수 있도록 필터를 구현한다

[해당 필터는 응답시에만 동작하게끔 설정 ]

 

 

Cookie로 응답온 CSRF토큰을 읽어들여서 Request Header 또는 Request Paramters를 통해 Request할때 CSRF토큰을 추가한다.

 

 

Multi Page Application

JavaScript가 각 페이지에서 로드되는 멀티 페이지 애플리케이션의 경우

CSRF 토큰을 쿠키에 노출시키는 대신 HTML 메타 태그 내에 CSRF 토큰을 포함시킬 수 있다

 

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<meta name="_csrf" th:content="${_csrf.token}"/>
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
</html>

HTML 메타 태그에 CSRF 토큰 포함한다

페이지를 서버에서 렌더링할 때, CSRF 토큰을 다음과 같이 HTML 메타 태그로 삽입할 수 있습니다.

JavaScript는 이 메타 태그에서 CSRF 토큰을 읽어들여 이후 요청 시 헤더에 포함할 수 있습니다.

function login() {
    const csrfHeader = $('meta[name="_csrf_header"]').attr('content');
    const csrfToken = $('meta[name="_csrf"]').attr('content')
    fetch('/api/login', {
    method: 'POST',
    headers: {[csrfHeader]: csrfToken }
})

AJAX 요청에서 CSRF 토큰 포함


메타 태그에 CSRF 토큰을 포함할 때의 장점

  1. 보안성 강화:
    • 쿠키에 CSRF 토큰을 포함할 경우, 브라우저는 쿠키를 자동으로 요청에 포함시키기 때문에 CSRF 공격에 더 취약할 수 있습니다.
    • 반면, 메타 태그에 CSRF 토큰을 포함하면 JavaScript로만 접근할 수 있으며, 브라우저가 자동으로 포함하지 않기 때문에 수동으로 요청에 포함해야 합니다. 이를 통해 악성 사이트가 요청을 자동으로 보내는 것을 막을 수 있습니다.
  2. 동일 출처 정책(Same-Origin Policy) 활용:
    • 메타 태그에 포함된 CSRF 토큰은 동일 출처 내에서만 접근 가능합니다. 다른 출처의 악성 JavaScript는 이 태그에 접근할 수 없으므로 CSRF 공격 위험을 줄일 수 있습니다.
  3. 구현 간편화:
    • 클라이언트가 쿠키 대신 메타 태그를 통해 CSRF 토큰을 얻기 때문에, 서버 측에서 쿠키 설정 등을 별도로 관리할 필요가 줄어들어 관리가 간편합니다.

이 방식은 특히 각 페이지가 새로 로드되는 멀티 페이지 애플리케이션에서 유용하며, 페이지가 로드될 때마다 서버가 최신 CSRF 토큰을 메타 태그에 포함할 수 있습니다.


 

 

 

댓글