CSRF 공격이란?
CSRF(Cross Site Request Forgery) 공격은 사용자가 의도하지 않은 요청을 악용하여 악의적인 행위를 유도하는 기법이다. 웹 애플리케이션은 일반적으로 사용자를 인증하기 위해 세션 쿠키나 인증 토큰을 사용하는데, CSRF 공격자는 이 인증 정보를 이용하여 피해자인 것처럼 요청을 보낸다. 이를 통해 공격자는 피해자의 권한으로 악의적인 동작을 수행할 수 있다. 예를 들어, 피해자가 로그인한 상태에서 공격자가 조작한 링크를 클릭하면, 웹 애플리케이션은 피해자의 인증 정보를 사용하여 공격자가 조작한 요청을 수행한다. 이로써 공격자는 피해자의 계정으로 의도하지 않은 동작을 수행할 수 있게 된다.
스프링 시큐리티에서는 기본적으로 CSRF 방지를 활성화한다. HttpSecurity 개체의 CSRF 메서드에 아래와 같이 기본 설정을 명시해 줄 수도 있다. 후술할 설정들도 명시하지 않으면 기본값을 사용하고, 그 기본값을 명시적으로 작성하거나 커스텀 설정으로 변경해서 사용이 가능하다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf(Customizer.withDefaults());
return http.build();
}
}
특정 엔드포인트나 경로에 대해서만 CSRF 방지를 사용하지 않으려면 다음과 같이 사용한다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf
.ignoringRequestMatchers("/api/*")
);
return http.build();
}
}
권장하지는 않지만, CSRF 방지를 사용하지 않으려면 다음과 같이 설정한다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf.disable());
return http.build();
}
}
CsrfFilter 구성요소
CSRF 방지는 CsrfFilter의 다음의 구성요소들을 사용하여 이뤄진다.
- CsrfTokenRepository
- CsrfTokenRequestHandler
- AccessDeniedHandler
- RequestMatcher
스프링 시큐리티 CSRF 방지 프로세스
- CSRF 필터는 CsrfTokenRepository에 토큰을 요청하고 DeferredCsrfToken을 받아온다.(성능 개선을 위해 지연된 로딩을 사용)
- CsrfTokenRequestHandler에 받아온 CsrfToken을 전달하여 애플리케이션의 다른 부분에서 사용가능하도록 요청 속성에 추가한다.
- RequestMatcher와 비교하여 CSRF 방지 대상이 아니면 다음 필터로 넘어가고, 대상이면 CSRF 방지 절차를 시작한다.
- 이제 지연됐던 DeferredCsrfToken에 CsrfToken이 완전히 로드되고, CsrfTokenRequestHandler를 사용하여 클라이언트가 제공한 실제 CsrfToken를 찾는다.
- 실제 CsrfToken이 저장된 CsrfToken와 같으면 절차가 종료된다. 만약 실패하면, AccessDeniedException이 발생하고 AccessDeniedHandler가 이를 처리한다.
https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-components
스프링 시큐리티는 기본적으로 세션에 CSRF 토큰을 저장하는 HttpSessionCsrfTokenRepository을 사용한다. 쿠키에 저장하는 CookieCsrfTokenRepository를 사용하거나 커스텀 리포지터리를 구현할 수도 있다. 이 리포지터리들은 요청 헤더 X-CSRF-TOKEN이나 요청 파라미터 _csrf에서 토큰을 가져온다. CookieCsrfTokenRepository 사용 시에는 이 토큰을 쿠키에 XSRF-TOKEN라는 이름으로 저장한다.
아래는 CookieCsrfTokenRepository을 사용하며 쿠키의 httpOnly 설정을 false로 설정한다. 이렇게 하면 자바스크립트에서 쿠키에 접근이 가능하기 때문에, Angular, React 등 자바스크립트를 사용하는 프레임워크나 AJAX 요청 시에도 쿠키에서 CSRF를 가져올 수 있다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
return http.build();
}
}
CSRF 토큰은 CsrfTokenRequestHandler를 사용하여 사용 가능한 상태로 만들어진다. 별도로 설정하지 않을 경우 스프링 시큐리티는 XorCsrfTokenRequestHandler를 사용한다. XorCsrfTokenRequestHandler는 추가적으로 BREACH 공격 방지 기능을 제공한다. 이 기능을 포함하지 않으려면 CsrfTokenRequestHandler를 사용하도록 설정하면 된다. BREACH 방지 기능은 CSRF 토큰을 무작위로 암호화하여 매 요청마다 반환받는 CSRF 토큰을 변경한다. 헤더나 파라미터의 토큰이 있으면 복호화하여 저장된 토큰과 비교하게 된다.
이제 서버로 요청을 보낼 때 CSRF 공격을 막기 위해서는 토큰을 요청에 (헤더나 파라미터 등 안에) 포함시켜서 보내야 한다. 폼 요청 시에는 다음과 같이 히든 필드를 사용한다.
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
타임리프나 스프링 폼 등을 사용하면 간편하게 토큰을 삽입할 수 있다. 만약 이런 기술들을 사용하지 않는다면 요청 속성에 "_csrf"라는 이름으로 토큰이 저장돼 있다는 사실을 이용해 JSTL로 다음과 같이 사용할 수도 있다.
<c:url var="logoutUrl" value="/logout"/>
<form action="${logoutUrl}"
method="post">
<input type="submit"
value="Log out" />
<input type="hidden"
name="${_csrf.parameterName}"
value="${_csrf.token}"/>
</form>
참조: https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html
Cross Site Request Forgery (CSRF) :: Spring Security
To handle an AccessDeniedException such as InvalidCsrfTokenException, you can configure Spring Security to handle these exceptions in any way you like. For example, you can configure a custom access denied page using the following configuration: Configure
docs.spring.io
'☕Java > Spring' 카테고리의 다른 글
스프링 국제화(Internationalization, I18N) (0) | 2024.04.02 |
---|---|
스프링 프로젝트에서 service 인터페이스나 serviceImpl 클래스를 사용하는 이유와 사용해야 하는지 여부 (0) | 2022.11.26 |
생성자 주입을 권장하는 이유 (0) | 2022.05.30 |
스프링 프레임워크를 사용하는 이유 (0) | 2022.05.30 |
Java EE(또는 Jakarta EE)에서 Spring Framework로 (0) | 2022.05.24 |