Spring Security (ten) [CSRF vulnerability protection]

10. CSRF Vulnerability Protection


Introduction

CSRF (Cross-Site Request Forgery), also known as one-click-attack, is usually abbreviated as CSRF or XSRF . CSRF attack is an attack method that hijacks users to send malicious requests on the currently logged-in browser. As opposed to XSS exploiting the user's trust in a given website. CSRF is to use the website's trust in the user's web browser . To put it simply, CSRF is an attacker who uses some technical means to deceive the user's browser to visit a website that the user has authenticated and perform malicious requests, such as sending emails, sending messages, and even property operations (such as transferring money and purchasing goods) . Since the client (browser) has been authenticated on the website, the website will think that the real user is operating and execute the request ( in fact, this is not the user's original intention )

  • give a simple example

Assuming that A is now logged into a bank's website to complete a transfer operation, the transfer link is as follows:
https://bank.xxx.com/withdraw?account=A&amount=1000&for=B
can be seen. This link is to transfer 1,000 yuan from account A to account B. Assuming that A does not log out and log in to the bank’s website, he opens a dangerous website in a new tab of the same browser. There is a picture in this dangerous website, and the code is as follows: < img src="
https://bankxxx.com /withdraw?account=A&amount=1000&for=C”>
Once the user opens the website, the request in the image link will be sent automatically. Since it is the same browser and the user has not logged out, the request will automatically carry the corresponding valid cookie information, and then complete a transfer operation. This is Cross Site Request Forgery

10.1 CSRF attack demonstration

Description: In a simulated scenario, user A transfers money to user B. Before user A logs out, someone transfers money to user C through user A's authenticated information.

Build:

  • The spring-security-11-csrf-bank service performs normal banking operations (port 8080)
  • spring-security-11-csrf-attack is used to simulate csrf cross-site request (port 8081)

attack demo

1) spring-security-11-csrf-bank module

  1. Create the module spring-security-11-csrf-bank and import dependenciespom.xml
<dependencies>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!--security-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
  1. Custom Security configuration
  • WebSecurityConfigurerAdapter
package com.vinjcent.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    
    


    // 自定义用户认证数据源(内存方式)
    @Bean
    public UserDetailsService userDetailsService() {
    
    
        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("admin").build());
        return inMemoryUserDetailsManager;
    }

    // 自定义数据源需要对外暴露
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.userDetailsService(userDetailsService());
    }

    // http 认证配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    

        http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .and()
                .csrf()
                .disable();     // 关闭 CSRF 跨站请求保护
    }
}
  1. Define the test controller interface
package com.vinjcent.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    
    

    @GetMapping("/index")
    public String toIndex() {
    
    
        return "index ok";
    }

    @PostMapping("/withdraw")
    public String withdraw() {
    
    
        System.out.println("第一次转账操作");
        return "执行第一次转账操作";
    }
}

2) spring-security-11-csrf-attack module

  1. Create the module spring-security-11-csrf-attack and import dependenciespom.xml
<dependencies>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
</dependencies>
  1. write index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>模拟 CSRF 跨站请求伪造</title>
</head>
<body>

<form action="http://localhost:8080/withdraw" method="post">
    <input type="hidden" name="name" value="A">
    <input type="hidden" name="money" value="B">
    <input type="submit" value="提交">
</form>

</body>
</html>

3) test

  1. First log in at spring-security-11-csrf-bank

insert image description here

  1. Then visit the spring-security-11-csrf-attack home page to make a request

insert image description here

  1. It can be seen that a request transfer was made from 8081 to 8080

insert image description here

insert image description here

summary

It can be found that after the user authenticates the identity of the 8080 normally, if another service knows the transfer interface of the 8080 service, it will operate the user's information according to this interface, which will bring data leakage to our users, because all Is the cookie information identifying the user on the current site

10.2 CSRF Defense

The root of CSRF attacks lies in the browser's default authentication mechanism (automatically carrying the cookie information of the current website). Although this mechanism can guarantee that the request is from a certain browser of the user, it cannot ensure that the request is authorized by the user. The request sent by the attacker and the user is exactly the same, which means that we have no way to directly reject a request here. If an additional parameter that cannot be obtained by the attacker can be carried in the legal request, the two different requests can be successfully distinguished, and the malicious request can be rejected directly. This mechanism is provided in Spring Security to defend against CSRF attacks, which we call令牌同步模式

Token Synchronization Mode

This is the current mainstream CSRF attack defense scheme. The specific operation method is to provide a secure, randomly generated string in each HTTP request, in addition to the default Cookie parameter, which we call CSRF token. This CSRF token is generated by the server and saved in HttpSession after generation. When the front-end request arrives, compare the CSRF token information carried in the request with the token saved in the server, and if the two are not equal, reject the HTTP request

NoteConsidering that some external sites are linked to our website, we require the request to be idempotent, so that there is no need to use CSRF tokens for methods such as HEAD, OPTIONS, TRACE, etc. Forcibly using them may lead to token leakage

  • Turn off CSRF request protected login page

After closing the CSRF request protection, the login page will not carry a csrf token token

insert image description here

  • Login page with CSRF request protection enabled

After enabling CSRF request protection, if the login page carries a csrf token token, and the 8081 service request is used again, it will be directly intercepted

insert image description here

insert image description here

10.3 Traditional web development using CSRF

After CSRF defense is enabled, the following code will be automatically added to the submitted form. If it cannot be added automatically, you need to manually add the following code after enabling it, and submit it with the request. The way to get the server token is as follows

<input th:name="${_csrf.parameterName}" type="hidden" th:value="{_csrf.token}" />

Environment build

  1. relypom.xml
<!--web-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--security-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--thymeleaf-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--thymeleaf-security-->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>
  1. application.ymlconfiguration file
server:
  port: 8080

spring:
  thymeleaf:
    mode: HTML
    suffix: .html
    prefix: classpath:/templates/
    cache: false
  1. Development test controller
package com.vinjcent.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloController {
    
    

    @PostMapping("/hello")
    @ResponseBody
    public String hello() {
    
    
        return "hello spring security!";
    }


    @RequestMapping("/toIndex")
    public String toIndex() {
    
    
        return "index";
    }
}
  1. Create index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <meta charset="UTF-8">
    <title>测试 CSRF 防御(传统web方式)</title>
</head>
<body>
<form th:action="@{/hello}" method="post">
    <input type="submit" value="提交">
</form>
</body>
</html>
  1. Security configuration
package com.vinjcent.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    
    

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.authorizeRequests().anyRequest()
                .authenticated()
                .and().formLogin()
                .and().csrf();	// 开启 csrf 跨域请求保护
    }
}
  1. test
  • In the absence of any configuration, the security configuration enables csrf request protection, and traditional web development will automatically add a form item to the form _csrf, as shown in the figure

insert image description here

10.4 Use CSRF for front-end and back-end separation

When the front and back ends are separated, you only need to put the generated csrf into the cookie, and obtain the token information in the cookie when requesting and submit it

Separation of front-end and back-end simulation

In the existing front-end and back-end separation authentication, modify the Security configuration, the core code is as follows

package com.vinjcent.config.security;

import com.vinjcent.filter.LoginFilter;
import com.vinjcent.handler.DivAuthenticationFailureHandler;
import com.vinjcent.handler.DivAuthenticationSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    
    

    // 使用内存数据源
    @Bean
    public UserDetailsService userDetailsService() {
    
    
        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("admin").build());
        return inMemoryUserDetailsManager;
    }

    // 配置认证管理者的认证数据源
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.userDetailsService(userDetailsService());
    }

    // 暴露自定义认证数据源
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
    
    
        return super.authenticationManagerBean();
    }

    // 创建自定义的LoginFilter对象
    @Bean
    public LoginFilter loginFilter() throws Exception {
    
    
        
        LoginFilter loginFilter = new LoginFilter();
        loginFilter.setFilterProcessesUrl("/login");
        loginFilter.setUsernameParameter("uname");
        loginFilter.setPasswordParameter("passwd");
        loginFilter.setAuthenticationManager(authenticationManager());
        loginFilter.setAuthenticationSuccessHandler(new DivAuthenticationSuccessHandler());
        loginFilter.setAuthenticationFailureHandler(new DivAuthenticationFailureHandler());
        return loginFilter;
    }

    // 请求拦截配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.authorizeRequests().anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .and()
                .csrf()
                // .disable();
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());    // 将令牌保存到 cookie 中,允许 cookie 前端获取

        // 替换原始 UsernamePasswordAuthenticationFilter 过滤器
        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

test

  • The first time you log in, the login fails because a csrf token is required

insert image description here

  • XSRF-TOKENAt the same time, the key-value is generated in the cookie , as shown in the figure below

insert image description here

Parsing the csrf authentication process

  • Perform Debug mode

insert image description here

  • You can see that some request types do not require token

insert image description here

  • You need to get the request header first, the default value isX-XSRF-TOKEN

insert image description here

  • First, it will be obtained from the request header Header. If it cannot be obtained, it will be _csrfobtained from the request parameter ( ).

insert image description here

  • Finally, compare the actual token with the current token

insert image description here

  • _csrfFinally, I found that if you need to realize the csrf function of front-end and back-end separation, either add a parameter named in the request parameter or carry a key-value pair key-value in the request headerX-XSRF-TOKEN

insert image description here

  • Certification successful display

insert image description here

Guess you like

Origin blog.csdn.net/Wei_Naijia/article/details/128436916