CSRF

简介: CSRF

CSRF

CSRF(Cross-Site Request Forgery)跨站请求伪造,是一种挟持用户在当前已登录的浏览器上发送恶意请求的攻击方法,比如攻击者通过一些技术手段欺骗用户的浏览器,去访问一个用户曾经认证过的网站并执行恶意请求。由于客户端已经在该网站认证过,所以该网站会认为是用户在操作二执行恶意请求。

CSRF的根源在于浏览器默认的身份验证机制(自动携带当前网站的Cookie信息),这种机制虽然可以保证请求是来自用户的某个浏览器,但是无法确保该请求是用户授权发送的,攻击者和用户发送的请求一模一样,如果在合法请求中额外携带一个攻击者无法获取的参数就可以成功区分出两种不同的请求,进而拒绝恶意请求

Spring 提供了两种机制:

  • 令牌同步模式
  • 在Cookie上知道SameSite属性

令牌同步模式

在每一个HTTP请求中,除了默认的自动携带的Cookie参数之外,再额外提供一个安全的,随机生成的字符串,我们称之为CSRF令牌。这个令牌由服务端生成,生成后在HttpSession中保存一份。当前请求到达之后,将请求携带的CSRF令牌信息和服务端中的令牌对比,如果两者不相等,则拒绝该Http请求。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/hello" method="post">
    <input type="hidden" th:value="${_csrf.token}" th:name="${_csrf.parameterName}">
    <input type="submit" value="hello">
</form>
</body>
</html>

form表单隐藏域中对应的key和value都是服务端默认返回的变量,只需要填充变量名即可。

请求方法要幂等性,所以Spring Security默认不会对GET,HEAD,OPTIONS以及TRACE请求进行CSRF令牌校验。

.csrf().disable();为关闭CSRF攻击防御功能,默认开启。

对应form表单请求,服务端返回的CSRF令牌,放在request属性中返回给前端。

对于Ajax请求,将CSRF令牌放在响应头Cookie中,开发者从Cookie中提取CSRF令牌信息,然后作为参数提交到服务端。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginProcessingUrl("/login.html")
            .successHandler((req,resp,auth)->{
                resp.getWriter().write("login success");
            })
            .permitAll()
            .and()
            .headers()
            .and()
            .csrf()
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}

csrfTokenRepository为CookieCsrfTokenRepository 设置HttpOnly为false,否则前端无法获取到Cookie中CSRF令牌

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
</head>
<body>
<div>
    <input type="text" id="username">
    <input type="password" id="password">
    <input type="button" value="登录" id="loginBtn">
</div>
<script>
    $("#loginBtn").click(function () {
        let _csrf = $.cookie('XSRF-TOKEN');
        $.post('/login.html', {
            username: $("#username").val(),
            password: $("#password").val(),
            _csrf: _csrf
        }, function (data) {
            alert(data);
        })
    })
</script>
</body>
</html>

区别:form表单服务端将CSRF令牌保存到HttpSession中,ajax请求是服务端将CSRF令牌放在Cookie中。

在Cookie上知道SameSite属性

通过在Cookie上指定SameSite属性,要求浏览器从外部站点发送请求时,不应携带Cookie信息,进而防止CSRF攻击。

SameSite属性:

  • Strict:只有同一站点发送的请求才包含Cookie信息,不同站点发送的请求不会包含Cookie信息。
  • Lax:同一站点发送的请求或者导航到目标地址的GET请求会自动包含Cookie信息,否则不包含Cookie信息。
  • None:Cookie将从所有上下文中发送,即允许跨域发送。
@Bean
public CookieSerializer httpSessionIdResolver(){
    DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
    cookieSerializer.setSameSite("strict");
    return cookieSerializer;
}

CsrfFilter过滤器

CsrfFilter是Spring Security过滤器链中的一环,在过滤器中校验客户端传来的CSRF令牌是否有效。

CsrfFilter过滤器是由CsrfConfigurer进行配置的,而CsrfConfigurer是在WebSecurityConfigurerAdapter的getHttp方法中添加进HttpSecurity中的。

CsrfFilter继承OncePerRequestFilter,最重要的方法就是doFilterInternal了

protected void doFilterInternal(HttpServletRequest request,
      HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
   request.setAttribute(HttpServletResponse.class.getName(), response);

   CsrfToken csrfToken = this.tokenRepository.loadToken(request);
   final boolean missingToken = csrfToken == null;
   if (missingToken) {
      csrfToken = this.tokenRepository.generateToken(request);
      this.tokenRepository.saveToken(csrfToken, request, response);
   }
   request.setAttribute(CsrfToken.class.getName(), csrfToken);
   request.setAttribute(csrfToken.getParameterName(), csrfToken);

   if (!this.requireCsrfProtectionMatcher.matches(request)) {
      filterChain.doFilter(request, response);
      return;
   }

   String actualToken = request.getHeader(csrfToken.getHeaderName());
   if (actualToken == null) {
      actualToken = request.getParameter(csrfToken.getParameterName());
   }
   if (!csrfToken.getToken().equals(actualToken)) {
      if (this.logger.isDebugEnabled()) {
         this.logger.debug("Invalid CSRF token found for "
               + UrlUtils.buildFullRequestUrl(request));
      }
      if (missingToken) {
         this.accessDeniedHandler.handle(request, response,
               new MissingCsrfTokenException(actualToken));
      }
      else {
         this.accessDeniedHandler.handle(request, response,
               new InvalidCsrfTokenException(csrfToken, actualToken));
      }
      return;
   }

   filterChain.doFilter(request, response);
}
  1. 首先调用tokenRepository.loadToken方法进行加载出CsrfToken对象 CsrfToken为接口,用来描述CSRF令牌信息,默认tokenRepository对象类型为LazyCsrfTokenRepository,CsrfTokenRepository是SpringSecurity中提供的CsrfToken保存接口,实现类有HttpSessionCsrfTokenRepository,CookieCsrfTokenRepository,LazyCsrfTokenRepository。HttpSessionCsrfTokenRepository是将 CsrfToken保存在HttpSession中,CookieCsrfTokenRepository是将CsrfToken保存在Cookie中,LazyCsrfTokenRepository是一个代理类,可以代理HttpSessionCsrfTokenRepository或者CookieCsrfTokenRepository,代理目的是延迟保存生成的CsrfToken。
  2. 如果CsrfToken对象不存在,则立马生成CsrfToken对象并保存起来。
  3. 将生成的CsrfToken对象设置到request属性中,这样我们在前端页面中就可以渲染出生成的令牌信息了。
  4. 调用requireCsrfProtectionMatcher.matches方法进行请求判断,该方法主要判断当前请求方法是否为GET,HEAD,TRACE以及OPTIONS。如果当前请求方法是这四种之一,则请求直接过,不用进行CSRF的令牌校验,上一步没有必须进行保存CsrfToken,LazyCsrfTokenRepository生成了CsrfToken令牌没有立即保存,而是后面调用getToken时才保存。
  5. 如果请求不是GET,HEAD,TRACE以及OPTIONS,先从请求头中提取出CSRF令牌,请求头没有,则从请求参数中提取出CSRF令牌,将拿到的CSRF令牌和第1步中通过loadToken加载出来的令牌进行对比,判断请求传来的CSRF令牌是否合法。

总结:请求到达后,会经过CsrfFilter,在该过滤器中,首先加载出保存的CsrfToken,可以是从HttpSession中加载,也可以是从请求头携带的Cookie中加载,默认是从HttpSession中加载,如果加载出来的CsrfToken为null,则立即生成一个CsrfToken并保存起来,由于默认tokenRepository类型是LazyCsrfTokenRepository,所以这里的保存并不是真正的保存,因为如果请求方法是GET,HEAD,TRACE以及OPTIONS,就没有必要保存。然后将生成的CsrfToken放到请求对象中,方面前端渲染。然后判断请求方法是否是需要进行CSRF令牌校验的方法,如果不是,则直接执行后面的过滤器,否则就从请求中拿出CSRF令牌信息和一开始加载出来的令牌进行对比。

CsrfAuthenticationStrategy

CsrfAuthenticationStrategy实现了SessionAuthenticationStrategy接口,默认也是由CompositeSessionAuthenticationStrategy代理执行,登录成功后触发执行,CsrfAuthenticationStrategy主要用于在登录成功后,删除旧的CsrfToken并生成一个新的CsrfToken

public void onAuthentication(Authentication authentication,
      HttpServletRequest request, HttpServletResponse response)
            throws SessionAuthenticationException {
   boolean containsToken = this.csrfTokenRepository.loadToken(request) != null;
   if (containsToken) {
      this.csrfTokenRepository.saveToken(null, request, response);

      CsrfToken newToken = this.csrfTokenRepository.generateToken(request);
      this.csrfTokenRepository.saveToken(newToken, request, response);

      request.setAttribute(CsrfToken.class.getName(), newToken);
      request.setAttribute(newToken.getParameterName(), newToken);
   }
}
相关文章
|
6月前
|
安全 数据安全/隐私保护
31、CSRF漏洞介绍
31、CSRF漏洞介绍
40 0
|
15天前
|
存储 安全 JavaScript
xss、csrf
【10月更文挑战第26天】防范 XSS 和 CSRF 攻击需要综合运用多种技术手段,从输入输出过滤、设置安全的 Cookie 属性、验证请求来源、添加令牌等多个方面入手,构建一个全面的安全防护体系,以确保网站和用户的安全。
|
8天前
|
网络安全 数据安全/隐私保护
什么是 CSRF 攻击
CSRF(跨站请求伪造)攻击是指攻击者诱导用户点击恶意链接或提交表单,利用用户已登录的身份在目标网站上执行非授权操作,如转账、修改密码等。这种攻击通常通过嵌入恶意代码或链接实现。
|
4月前
|
运维 安全 Java
什么是 CSRF?如何防止 CSRF 攻击?
CSRF 攻击是一种常见且危险的 Web 安全漏洞,攻击者可以通过伪造用户请求,执行恶意操作,作为程序员,为了防御 CSRF 攻击,常见的策略包括使用 CSRF Token、检查 Referer 或 Origin 头、设置 SameSite Cookie 属性以及双重提交 Cookie。 因为程序员对于 CSRF 攻击可以做的事情还是很有限,所以,承担主要责任的是安全部门或者运维部门,但是作为程序员,我们需要具备这些安全意识,在安全等级比较高的需求中也需要把这些安全因素考虑在内。
|
5月前
|
安全 前端开发 JavaScript
详细解读CSRF漏洞详解
详细解读CSRF漏洞详解
68 0
|
6月前
|
存储 JavaScript 安全
CSRF和XSS是什么?
CSRF和XSS是什么?
36 0
|
6月前
|
网络安全 数据安全/隐私保护
CSRF 和 XSS 理解
CSRF 和 XSS 理解
129 0
|
6月前
|
安全 前端开发 Java
什么是 CSRF 攻击?
什么是 CSRF 攻击?
|
数据安全/隐私保护
CSRF(跨站请求伪造)
CSRF(跨站请求伪造)
123 0
|
存储 安全 PHP
CSRF 攻击
本文主要介绍了什么是 CSRF 攻击,以及如何防御 CSRF 攻击
236 2