SpringSecurity(十)【CSRF 漏洞保护】

电玩女神 2024-03-30 12:08 232阅读 0赞

十、CSRF 漏洞保护


简介

CSRF(Cross-Site Request Forgery 跨站请求伪造),也可称为一键式攻击(one-click-attack)通常缩写为 CSRF 或者 XSRFCSRF 攻击是一种挟持用户在当前已登录的浏览器上,发送恶意请求的攻击方法。相对于 XSS 利用用户对指定网站的信任。CSRF则是利用网站对用户网页浏览器的信任。简单来说, CSRF 是致击者通过一些技术手段欺骗用户的浏览器,去访问一个用户曾经认证过的网站并执行恶意请求,例如发送邮件、发消息、甚至财产操作(如转账和购买商品)。由于客户端(浏览器)已经在该网站上认证过,所以该网站会认为是真正用户在操作而执行请求(实际上这个并非用户的本意

  • 举个简单的例子

假设 A 现在登录了某银行的网站准备完成一项转账操作,转账的链接如下:
https://bank.xxx.com/withdraw?account=A&amount=1000&for=B
可以看到。这个链接是想从 A 这个账户下转账1000元到 B 账户下。假设 A 没有注销登录该银行的网站,就在同一个浏览器新的选项卡中打开了一个危险网站,这个危险网站中有一幅图片,代码如下:
< img src=“https://bankxxx.com/withdraw?account=A&amount=1000&for=C”>
一旦用户打开了这个网站,这个图片链接中的请求就会自动发送出去。由于是同一个浏览器并且用户尚未注销登录,所以该请求会自动携带上对应的有效的Cookie信息,进而完成一次转账操作。这就是跨站请求伪造

10.1 CSRF 攻击演示

说明:模拟场景,用户A给用户B转账,在用户A未注销之前,有人通过用户A已经认证的信息,对其进行转账给用户C的操作

搭建:

  • spring-security-11-csrf-bank 服务进行正常银行操作(8080端口)
  • spring-security-11-csrf-attack 用于模拟 csrf 跨站请求(8081端口)

攻击演示

1) spring-security-11-csrf-bank 模块

  1. 创建模块 spring-security-11-csrf-bank,导入依赖pom.xml




    org.springframework.boot
    spring-boot-starter-security



    org.springframework.boot
    spring-boot-starter-web

  2. 自定义 Security 配置

    • 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 {

  1. // 自定义用户认证数据源(内存方式)
  2. @Bean
  3. public UserDetailsService userDetailsService() {
  4. InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
  5. inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("admin").build());
  6. return inMemoryUserDetailsManager;
  7. }
  8. // 自定义数据源需要对外暴露
  9. @Override
  10. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  11. auth.userDetailsService(userDetailsService());
  12. }
  13. // http 认证配置
  14. @Override
  15. protected void configure(HttpSecurity http) throws Exception {
  16. http.authorizeRequests()
  17. .anyRequest()
  18. .authenticated()
  19. .and()
  20. .formLogin()
  21. .and()
  22. .csrf()
  23. .disable(); // 关闭 CSRF 跨站请求保护
  24. }
  25. }
  1. 定义测试controller接口

    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 {

  1. @GetMapping("/index")
  2. public String toIndex() {
  3. return "index ok";
  4. }
  5. @PostMapping("/withdraw")
  6. public String withdraw() {
  7. System.out.println("第一次转账操作");
  8. return "执行第一次转账操作";
  9. }
  10. }

2) spring-security-11-csrf-attack 模块

  1. 创建模块 spring-security-11-csrf-attack,导入依赖pom.xml




    org.springframework.boot
    spring-boot-starter-security

  2. 编写index.html

    <!DOCTYPE html>




    模拟 CSRF 跨站请求伪造










3)测试

  1. 先在 spring-security-11-csrf-bank 进行登录

在这里插入图片描述

  1. 然后再访问 spring-security-11-csrf-attack 主页进行请求

在这里插入图片描述

  1. 可以看到从8081进行了一次对8080的请求转账

在这里插入图片描述

在这里插入图片描述

小结

可以发现,当用户在8080正常认证身份之后,假如另外一台服务知道8080服务的转账接口,那么就会根据这个接口去操作用户的信息,这回给我们用户带来数据泄露的问题,因为都是在当前网站的 Cookie 信息识别用户

10.2 CSRF 防御

CSRF 攻击的根源在于浏览器默认的身份验证机制(自动携带当前网站的Cookie信息)。这种机制虽然可以保证请求是来自用户的某个浏览器,但是无法确保这请求是用户授权发送。攻击者和用户发送的请求一模一样,这意味着我们没有办法去直接拒绝这里的某一个请求。如果能在合法清求中额外携带一个攻击者无法获取的参数,就可以成功区分出两种不同的请求,进而直接拒绝掉恶意请求。在 SpringSecurity 中就提供了这种机制来防御 CSRF 攻击,这种机制我们称之为令牌同步模式

令牌同步模式

这是目前主流的 CSRF 攻击防御方案。具体的操作方式就是在每一个 HTTP 请求中,除了默认自动携带的 Cookie 参数之外,再提供一个安全的、随机生成的字符串,我们称之为 CSRF 令牌。这个 CSRF 令牌由服务端生成,生成后在 HttpSession 中保存一份。当前端请求到达后,将请求携带的 CSRF 令牌信息和 服务端中保存的令牌进行对比,如果两者不相等,则拒绝掉该 HTTP 请求

】考虑到有一些外部站点链接到我们的网站,所以我们要求请求是幂等的,这样对于 HEAD、OPTIONS、TRACE等方法就没有必要使用 CSRF 令牌了,强行使用可能会导致令牌泄露

  • 关闭 CSRF 请求保护的登录页面

在关闭 CSRF 请求保护之后,登陆页面是不会携带一个 csrf 的 token 令牌的

在这里插入图片描述

  • 开启 CSRF 请求保护的登录页面

在开启 CSRF 请求保护之后,登陆页面携带了一个 csrf 的 token 令牌的,并且再次使用8081服务请求,会直接拦截

在这里插入图片描述

在这里插入图片描述

10.3 传统 web 开发使用 CSRF

开启 CSRF 防御后会自动在提交的表单加入如下代码,如果不能自动加入,需要开启之后手动加入如下代码,并随着请求提交。获取服务端令牌方式如下

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

环境搭建

  1. 依赖pom.xml


    org.springframework.boot
    spring-boot-starter-web



    org.springframework.boot
    spring-boot-starter-security



    org.springframework.boot
    spring-boot-starter-thymeleaf



    org.thymeleaf.extras
    thymeleaf-extras-springsecurity5
    3.0.4.RELEASE
  2. application.yml配置文件

    server:
    port: 8080

    spring:
    thymeleaf:

    1. mode: HTML
    2. suffix: .html
    3. prefix: classpath:/templates/
    4. cache: false
  3. 开发测试 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 {

  1. @PostMapping("/hello")
  2. @ResponseBody
  3. public String hello() {
  4. return "hello spring security!";
  5. }
  6. @RequestMapping("/toIndex")
  7. public String toIndex() {
  8. return "index";
  9. }
  10. }
  1. 创建 index.html

    <!DOCTYPE html>




    测试 CSRF 防御(传统web方式)






  2. Security 配置

    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 {

  1. @Override
  2. protected void configure(HttpSecurity http) throws Exception {
  3. http.authorizeRequests().anyRequest()
  4. .authenticated()
  5. .and().formLogin()
  6. .and().csrf(); // 开启 csrf 跨域请求保护
  7. }
  8. }
  1. 测试

    • 在没有任何配置情况下,security 配置开启了 csrf 请求保护,传统的 web 开发会自动在表单中添加一个表单项 _csrf,如图所示

在这里插入图片描述

10.4 前后端分离使用 CSRF

前后端分离时,只需要将生成 csrf 放入 Cookie 中,并在请求时获取 Cookie 中令牌信息进行提交即可

模拟前后端分离

在已有的前后端分离认证中,修改 Security 配置,核心代码如下

  1. package com.vinjcent.config.security;
  2. import com.vinjcent.filter.LoginFilter;
  3. import com.vinjcent.handler.DivAuthenticationFailureHandler;
  4. import com.vinjcent.handler.DivAuthenticationSuccessHandler;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7. import org.springframework.security.authentication.AuthenticationManager;
  8. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
  9. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
  10. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
  11. import org.springframework.security.core.userdetails.User;
  12. import org.springframework.security.core.userdetails.UserDetailsService;
  13. import org.springframework.security.provisioning.InMemoryUserDetailsManager;
  14. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
  15. import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
  16. @Configuration
  17. public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
  18. // 使用内存数据源
  19. @Bean
  20. public UserDetailsService userDetailsService() {
  21. InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
  22. inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("admin").build());
  23. return inMemoryUserDetailsManager;
  24. }
  25. // 配置认证管理者的认证数据源
  26. @Override
  27. protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  28. auth.userDetailsService(userDetailsService());
  29. }
  30. // 暴露自定义认证数据源
  31. @Override
  32. @Bean
  33. public AuthenticationManager authenticationManagerBean() throws Exception {
  34. return super.authenticationManagerBean();
  35. }
  36. // 创建自定义的LoginFilter对象
  37. @Bean
  38. public LoginFilter loginFilter() throws Exception {
  39. LoginFilter loginFilter = new LoginFilter();
  40. loginFilter.setFilterProcessesUrl("/login");
  41. loginFilter.setUsernameParameter("uname");
  42. loginFilter.setPasswordParameter("passwd");
  43. loginFilter.setAuthenticationManager(authenticationManager());
  44. loginFilter.setAuthenticationSuccessHandler(new DivAuthenticationSuccessHandler());
  45. loginFilter.setAuthenticationFailureHandler(new DivAuthenticationFailureHandler());
  46. return loginFilter;
  47. }
  48. // 请求拦截配置
  49. @Override
  50. protected void configure(HttpSecurity http) throws Exception {
  51. http.authorizeRequests().anyRequest()
  52. .authenticated()
  53. .and()
  54. .formLogin()
  55. .and()
  56. .csrf()
  57. // .disable();
  58. .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()); // 将令牌保存到 cookie 中,允许 cookie 前端获取
  59. // 替换原始 UsernamePasswordAuthenticationFilter 过滤器
  60. http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
  61. }
  62. }

测试

  • 第一次登录,登陆失败,原因是需要一个 csrftoken 令牌

在这里插入图片描述

  • 同时,在 Cookie 中生成了XSRF-TOKEN的key-value,如下图所示

在这里插入图片描述

解析 csrf 认证流程

  • 进行 Debug 调式

在这里插入图片描述

  • 可以看到有些请求类型不需要 token

在这里插入图片描述

  • 需要先获取请求头,默认值为X-XSRF-TOKEN

在这里插入图片描述

  • 首先会去请求头Header中获取,如果获取不到,就会去请求参数(_csrf)中获取

在这里插入图片描述

  • 最后将实际的 token 与当前的 token 进行比对

在这里插入图片描述

  • 最后发现,如果需要实现前后端分离的 csrf 功能,要么在请求参数中添加一个名为_csrf的参数或请求头 header 中携带一个X-XSRF-TOKEN键值对key-value

在这里插入图片描述

  • 认证成功展示

在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,232人围观)

还没有评论,来说两句吧...

相关阅读