Skip to content

CSRF - Cross-Site Request Forgery#

What Is The CSRF?#

  • Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they’re currently authenticated. With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker’s choosing. If the victim is a normal user, a successful CSRF attack can force the user to perform state changing requests like transferring funds, changing their email address, and so forth. If the victim is an administrative account, CSRF can compromise the entire web application.
  • Consider there are two websites, a victim is using the wedsite abc.com and the attacker's website is xyz.com. Also assume that the victim is logged in and his session is being maintained by Cookie. The attacker will:
    • Assume that the attacker knows the api for changing password of abc.com website with a POST request which contains the new password as a parameter.
    • Then the attacker will place the request with change password in the HTML code on his website xyz.com. So that, It will imitate a legal request to abc.com (for example, a form with method as post and a hidden input field that contains the new password).
    • So the attacker will make sure that the form is submitted by either using "autosubmit" when the HTML page of xyz.com is loaded or luring the victim to click on a submit button.
    • When the victim visits xyz.com and that form is submitted, the victim's Web Browser makes a request to abc.com for a password change. Also the browser appends the session cookie automatically with the request. The server treats it as a genuine request and reset the victim's password to the attacker's supplied value. This way the victim's account gets taken over by the attacker.
  • There are many proposed way to implement CSRF protection on server side, among which the use of CSRF tokens is most popular. A CSRF token is a string that is tied to a user's Session but is not submitted automatically.
  • For details, the server will generate a CSRF token and put in the response cookie for user's browser, so when the user on the abc.com makes a request to the server, the token will be added into the request header by abc.com and the server will check this token is valid or not to continue. Then if the user loads the xyz.com or click the submit button on xyz.com the request will also go to the server, however there is no the CSRF token in the request header because the browser is only add the session cookie automatically into the request header so the server will check this request and there is no CSRF token and the request will be rejected. If the attacker try to add the CSRF token header into the request so there is no way for him to know what is the valid token value that the user is using on abc.com.

 #zoom

CSRF In Spring Security#

Disable Default CSRF Protection#

  • By default, any web application or web framework will stop the communication if someone is using POST, PUT and DELETE Http methods which will potentially alter the data using causative issue.
  • So, if you start your spring security application and try calling apis with POST, PUT and DELETE methods, you would see the error code 403 although you put the correct credentials for calling those apis.

 #zoom

  • In case your spring boot application is running in the internal network behind firewalls and the outside hackers can not access your links. Then you can simply disable CSRF inside your spring security framework.
ProjectSecurityConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.spring.security.spring.security.custom.csrf.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;

@Configuration
public class ProjectSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     *
     * contact: Not Secure
     * notice: Not Secure
     * balance: Secure
     * Card: Secure
     * Loan: Secure
     * Account: Secure
     *
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .cors()
                .and().authorizeRequests()
                .antMatchers("/v1/user").authenticated()
                .antMatchers("/v1/accounts/**").authenticated()
                .antMatchers("/v1/balance").authenticated()
                .antMatchers("/v1/loan").authenticated()
                .antMatchers("/v1/card").authenticated()
                .antMatchers("/v1/contact").permitAll()
                .antMatchers("/v1/notice").permitAll()
                .and().formLogin()
                .and().httpBasic();
    }

    @Bean
    protected CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
        corsConfiguration.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
  • We can use http.csrf().disable() to disable default CSRF in spring security.
  • Now, let's start your spring security application and call POST api again, you should see the 200 status as below.

 #zoom

Generate CSRF Token#

  • As discussed above, using CSRF tokens is most popular way that we can protect our users from CSRF attacks, so we will configure our spring security application to generate a CSRF tokens which will be added into the response cookies.
ProjectSecurityConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.spring.security.spring.security.custom.csrf.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;

@Configuration
public class ProjectSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     *
     * contact: Not Secure
     * notice: Not Secure
     * balance: Secure
     * Card: Secure
     * Loan: Secure
     * Account: Secure
     *
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf()
                .ignoringAntMatchers("/v1/user")
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .and().cors()
                .and().authorizeRequests()
                .antMatchers("/v1/user").authenticated()
                .antMatchers("/v1/accounts/**").authenticated()
                .antMatchers("/v1/balance").authenticated()
                .antMatchers("/v1/loan").authenticated()
                .antMatchers("/v1/card").authenticated()
                .antMatchers("/v1/contact").permitAll()
                .antMatchers("/v1/notice").permitAll()
                .and().formLogin()
                .and().httpBasic();
    }

    @Bean
    protected CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
        corsConfiguration.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
  • If you look into the configure() method, you can see we will firstly configure ignoring CSRF protection for specific apis by http.csrf().ignoringAntMatchers(<path pattern>) so we can use POST, PUT and DELETE methods with CSRF token for those apis.
  • Then to generate CSRF token and put it in the response cookies we will use .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) method. So our spring security application will generate a CSRF token string and put it into a cookie with HttpOnly is false. The HttpOnly: false means that the FE side can use script to access and get the value of the cookie, if the HttpOnly: true the FE side can't use script to access and get value of the cookie, if the developer try to use script to get this cookie value, he will be received an empty string by default. However, the browser in some case, can still send the value of cookie to the server although the cookie with parameter HttpOnly: true. This is the reason why you can see the session cookie with HttpOnly: true but you still see it is appended in the request to server, this is the feature of the browser.
  • Go back to the CSRF token, we have the HttpOnly: false so it means the FE side have to use script to collect the CSRF token cookie and add it's value into request's header, this is not the session cookie so the browser will not do it automatically for you.
  • Now, let's start your spring security application and call the post api again, you will received the status 200 and one more cookie with name XSRF-TOKEN, so it is the CSRF token cookie.

 #zoom

See Also#

References#