精华内容
下载资源
问答
  • 2022-05-21 14:58:22

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

    更多相关内容
  • Spring Security CSRF防御&源码分析

    千次阅读 2022-01-14 22:45:38
    Spring Security CSRF防御&源码分析

    一、CSRF简介

    1、CSRF是什么?

    CSRF(Cross-site request forgery),也被称为:one click attack/session riding,中文名称:跨站请求伪造,通常缩写为:CSRF/XSRF

    跨站请求攻击:简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。
    由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。
    这利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。

    图来自网络:
    在这里插入图片描述

    2、CSRF攻击防御措施

    1)检查 HTTP Referer 字段
    HTTP头中有一个 Referer字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer字段应和请求的地址位于同一域名下。
    以上文银行操作为例,Referer字段地址通常应该是转账按钮所在的网页地址,应该也位于www.examplebank.com之下。而如果是CSRF攻击传来的请求,Referer字段会是包含恶意网址的地址,不会位于www.examplebank.com之下,这时候服务器就能识别出恶意的访问。

    这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的Referer字段。虽然http协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其 Referer字段的可能。

    2)在请求地址中添加 token 并验证
    由于 CSRF的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在 cookie中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再运行 CSRF攻击。这种数据通常是窗体中的一个数据项。服务器将其生成并附加在窗体中,其内容是一个伪随机数。当客户端通过窗体提交请求时,这个伪随机数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪随机数,而通过 CSRF传来的欺骗性攻击中,攻击者无从事先得知这个伪随机数的值,服务端就会因为校验 token的值为空或者错误,拒绝这个可疑请求。

    二、Spring Security支持CSRF防御

    从 Spring Security 4.0 开始,默认情况下会启用 CSRF 保护,以防止 CSRF 攻击应用程序。
    Spring Security CSRF 会针对除了 “GET”, “HEAD”, “TRACE”, "OPTIONS"四类的请求进行防御。

    1、核心类

    1.1 CsrfToken接口

    CsrfToken接口定义了获取消息头、请求参数、令牌等API。

    public interface CsrfToken extends Serializable {
        String getHeaderName();
    
        String getParameterName();
    
        String getToken();
    }
    

    默认的实现类为: DefaultCsrfToken

    1.2 CsrfTokenRepository接口

    CsrfTokenRepository接口定义了生成、保存、加载 CsrfToken等API。

    public interface CsrfTokenRepository {
        CsrfToken generateToken(HttpServletRequest var1);
    
        void saveToken(CsrfToken var1, HttpServletRequest var2, HttpServletResponse var3);
    
        CsrfToken loadToken(HttpServletRequest var1);
    }
    

    默认的实现类为:HttpSessionCsrfTokenRepository

    • HttpSessionCsrfTokenRepository是一个基于 HttpSession保存 csrf token的存储实现。

    • CookieCsrfTokenRepository会将 csrf token以名 XSRF-TOKEN写入 Cookie,然后接收请求时从名为 X-XSRF-TOKEN的 header或者名为 _csrf的 http请求参数读取 csrf token。但是CookieCsrfTokenRepository写入的 Cookie默认具有 cookieHttpOnly属性,前端 js是不能操作它的,这就需要在 Spring Security的配置中将 cookieHttpOnly属性设为false

    • LazyCsrfTokenRepository,它是一个代理,可以用来增强 HttpSessionCsrfTokenRepository 或者 CookieCsrfTokenRepository 的功能

    1.3 CsrfFilter过滤器

    CsrfFilter过滤器用于处理跨站请求伪造。
    HttpSession 或者 Cookie 中的 csrf token值与内存中保存的的是否一致,

    • 如果一致框架则认为当然登录页面是安全的,
    • 如果不一致,会报403forbidden错误。

    注意:

    • 如果使用 Spring Security 自带的默认登录页面时,可以看到默认登录页面是加了隐藏域。
    • 如果使用自定义的登录页面时,我们需要手动添加这个隐藏域。
    • 如果是前后端分离的项目,可以放在 Cookie 中。

    2、代码实现

    Spring Security 默认是开启 CSRF 保护的。我们可以指定 CSRF使用哪个 CsrfTokenRepository实现类。在 Spring Security配置类中添加。

    .csrf()
    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) //
    

    我们使用 CookieCsrfTokenRepository将 令牌放在 Cookie 中返回前端,前端先从 Cookie 中提取出 XSRF-TOKEN,放到 _csrf _csrf 参数中,通过一个 POST 请求执行登录操作。

    启动项目,点击登录,我们就可以看到 Cookie中看到一个 XSRF-TOKEN。登录成功

    3、源码分析

    HttpSessionCsrfTokenRepository方式自行查看。重点查看 CookieCsrfTokenRepository类。

    3.1 CSRF 的原理

    • 生成 csrfToken 保存到 HttpSession 或者 Cookie 中
    • 请求到来时,从请求中提取 csrfToken,和保存的 csrfToken 做比较,进而判断当前请求是否合法。主要通过 CsrfFilter 过滤器来完成。

    3.2 csrf token生成

    查看 CookieCsrfTokenRepository类的 generateToken方法。XSRF-TOKEN的值默认是一个 uuid。
    在这里插入图片描述

    思考:它是什么时候被生产的呢?

    3.3 csrf token校验

    请求会经过 CsrfFilter过滤器处理,查看 它的父类的 doFilter方法,会调用它子类 CsrfFilter的 doFilterInternal方法

     //看出SpringSecurity的csrf机制把请求方式分成两类来处理
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                        FilterChain filterChain) throws ServletException, IOException {
            request.setAttribute(HttpServletResponse.class.getName(), response);
            //获取令牌
            CsrfToken csrfToken = this.tokenRepository.loadToken(request);
            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);
    		//1."GET", "HEAD", "TRACE", "OPTIONS"四类请求可以直接通过
            if (!this.requireCsrfProtectionMatcher.matches(request)) {
                filterChain.doFilter(request, response);
            } else {
    			//2.除去上面四类,包括POST都要被验证携带token才能通过
                //从Header或者Parameter中获取前端的token
                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));
                    }
                } else {
                    filterChain.doFilter(request, response);
                }
            }
        }
    

    逻辑如下:

    • 在tokenRepository查询后端保存的令牌。
    • 如果是 GET等四类请求则不会使用 CSRF防御,直接放行
    • 如果使用 CSRF防御,就从Header或者Parameter中获取前端的token,然后对前后端的令牌进行校验。如果校验不通过,抛出 InvalidCsrfTokenException异常,并调用访问拒绝处理器进行处理。如果通过就放行

    1)在tokenRepository查询后端保存的令牌
    在这里插入图片描述

    2)调用requireCsrfProtectionMatcher.matches判断请求方法是否是GET,HEAD,TRACE,OPTIONS,匹配则不会使用CSRF防御。直接放行
    在这里插入图片描述

    3)使用 CSRF防御,令牌校验
    在这里插入图片描述

    参考文章:

    • 什么是CSRF:https://blog.csdn.net/weixin_40482816/article/details/114301717

    – 求知若饥,虚心若愚。

    展开全文
  • 背景1、什么是CSRF攻击?这里不再介绍CSRF,已经了解CSRF原理的同学可以直接跳到:“3、前后端分离下有何不同?”。不太了解的同学可以看这两篇对CSRF介绍比较详细...2、有哪些防御方案?上面这个例子当然有点危言耸...

    背景

    1、什么是CSRF攻击?

    这里不再介绍CSRF,已经了解CSRF原理的同学可以直接跳到:“3、前后端分离下有何不同?”。

    不太了解的同学可以看这两篇对CSRF介绍比较详细的参考文章:

    如果来不及了解CSRF的原理,可以这么理解:有一个人发给你一个搞(mei)笑(nv)图片链接,你打开这个链接之后,便立刻收到了短信:你的银行里的钱已经转移到这个人的帐户了。

    2、有哪些防御方案?

    上面这个例子当然有点危言耸听,当然可以确定的是确实会有这样的漏洞:你打开了一个未知域名的链接,然后你就自动发了条广告帖子、你的Gmail的邮件内容就泄露了、你的百度登录状态就没了……

    防御方案在上面的两篇文章里也有提到,总结下,无外乎三种:

    用户操作限制,比如验证码;

    请求来源限制,比如限制HTTP Referer才能完成操作;

    token验证机制,比如请求数据字段中添加一个token,响应请求时校验其有效性;

    第一种方案明显严重影响了用户体验,而且还有额外的开发成本;第二种方案成本最低,但是并不能保证100%安全,而且很有可能会埋坑;第三种方案,可取!

    token验证的CSRF防御机制是公认最合适的方案,也是本文讨论的重点。

    3、前后端分离下有何不同?

    《CSRF 攻击的应对之道》这篇文章里有提到:

    要把所有请求都改为 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接受的

    我们前端架构早已经告别了服务端语言(PHP/JAVA等)绑定路由、携带数据渲染模板引擎的方式(毕竟是2011年的文章了,我们笑而不语)。

    当然, 前端不要高兴的太早:前后端分离之后,Nodejs不具备完善的服务端SESSION、数据库等功能。

    总结一下,在“更先进”的前端架构下,与以往的架构会有一些区别:

    Nodejs层不处理SESSION,无法直接实现会话状态数据保存;

    所有的数据通过Ajax异步获取,可以灵活实现token方案;

    实现思路

    如上文提到,这里仅仅讨论在“更先进”的前端后端架构背景下的token防御方案的实现。

    1、可行性方案

    token防御的整体思路是:

    第一步:后端随机产生一个token,把这个token保存在SESSION状态中;同时,后端把这个token交给前端页面;

    第二步:下次前端需要发起请求(比如发帖)的时候把这个token加入到请求数据或者头信息中,一起传给后端;

    第三步:后端校验前端请求带过来的token和SESSION里的token是否一致;

    上文提到过,前后端分离状态下,Nodejs是不具备SESSION功能的。那这种token防御机制是不是就无法实现了呢?

    肯定不是。我们可以借助cookie把这个流程升级下:

    第一步:后端随机产生一个token,基于这个token通过SHA-56等散列算法生成一个密文;

    第二步:后端将这个token和生成的密文都设置为cookie,返回给前端;

    第三步:前端需要发起请求的时候,从cookie中获取token,把这个token加入到请求数据或者头信息中,一起传给后端;

    第四步:后端校验cookie中的密文,以及前端请求带过来的token,进行正向散列验证;

    当然这样实现也有需要注意的:

    散列算法都是需要计算的,这里会有性能风险;

    token参数必须由前端处理之后交给后端,而不能直接通过cookie;

    cookie更臃肿,会不可避免地让头信息更重;

    现在方案确定了,具体该如何实现呢?

    2、具体实现

    我们的技术栈是 koa(服务端) + Vue.js(前端) 。有兴趣可以看这些资料:

    在服务端,实现了一个token生成的中间件,koa-grace-csrf:

    // 注意:代码有做精简

    const tokens = require('./lib/tokens');

    return function* csrf(next) {

    let curSecret = this.cookies.get('密文的cookie');

    // 其他如果要获取参数,则为配置参数值

    let curToken = '请求http头信息中的token';

    // token不存在

    if (!curToken || !curSecret) {

    return this.throw('CSRF Token Not Found!',403)

    }

    // token校验失败

    if (!tokens.verify(curSecret, curToken)) {

    return this.throw('CSRF token Invalid!',403)

    }

    yield next;

    // 无论何种情况都种两个cookie

    // cookie_key: 当前token的cookie_key,httpOnly

    let secret = tokens.secretSync();

    this.cookies.set(options.cookie_key, secret);

    // cookie_token: 当前token的的content,不需要httpOnly

    let newToken = tokens.create(secret);

    this.cookies.set(options.cookie_token, newToken)

    }

    在前端代码中,对发送ajax请求的封装稍作优化:

    this.$http.post(url, data, {

    headers: {

    'http请求头信息字段名': 'cookie中的token'

    }

    }).then((res) => {})

    总结一下:

    Nodejs生成一个随机数,通过随机数生成散列密文;并将随机数和密文存到cookie;

    客户端JS获取cookie中的随机数,通过http头信息交给Nodejs;

    Nodejs响应请求,校验cookie中的密文和头信息中的随机数是否匹配;

    这里依旧有个细节值得提一下:Nodejs的上层一般是nginx,而nginx默认会过滤头信息中不合法的字段(比如头信息字段名包含“_”的),这里在写头信息的时候需要注意。

    "One more thing..."

    上文也提到,通过cookie及http头信息传递加密token会有很多弊端;有没有更优雅的实现方案呢?

    1、cookie中samesite属性

    回溯下CSRF产生的根本原因:cookie会被第三方发起的跨站请求携带,这本质上是HTTP协议设计的漏洞。

    那么,我们能不能通过cookie的某个属性禁止cookie的这个特性呢?

    好消息是,在最新的RFC规范中已经加入了“samesite”属性。细节这里不再赘述,可以参考:

    2、更优雅的架构

    当然,目前为止,客户端对samesite属性的支持并不是特别好;回到前后端分离架构下,我们明确下前后端分离框架的基本原则:

    后端(Java / PHP )职责:

    服务层颗粒化接口,以便前端Nodejs层异步并发调用;

    用户状态保存,实现用户权限等各种功能;

    前端(Nodejs + Javascript)职责:

    Nodejs层完成路由托管及模板引擎渲染功能

    Nodejs层不负责实现任何SESSION和数据库功能

    我们提到,前端Nodejs层不负责实现任何SESSION和数据库功能,但有没有可能把后端缓存系统做成公共服务提供给Nodejs层使用呢?想想感觉前端整条路都亮了有木有?!这里先挖一个坑,后续慢慢填。

    3、延伸

    这里再顺便提一下,新架构下的XSS防御。

    犹记得,在狼厂使用PHP的年代,经常被安全部门曝出各类XSS漏洞,然后就在smaty里添加各种escape滤镜,但是添加之后发现竟然把原始数据也给转义了。

    当然,现在更多要归功于各种MVVM单页面应用:使得前端完全不需要通过读取URL中的参数来控制VIEW。

    不过,还有一点值得一提:前后端分离框架下,路由由Nodejs控制;我自己要获取的后端参数和需要用在业务逻辑的参数,在主观上前端同学更好把握一些。

    所以, 在koa(服务端) + Vue.js(前端)架构下基本不用顾虑XSS问题(至少不会被全安组追着问XSS漏洞啥时候修复)。

    总结

    要不学PHP、看Java、玩Python做全栈好了?

    展开全文
  • SpringSecurity - CSRF 防御

    2021-10-20 06:17:51
    SpringSecurity CSRF 防御,我们使用http.csrf.disable()暂时关闭掉了CSRF防御功能,但是这样是不安全的,那么怎么样才是正确的做法呢? 整体来说,就是两个思路: 生成 csrfToken 保存在 HttpSession 或者 ...

    SpringSecurity CSRF 防御,我们使用http.csrf.disable()暂时关闭掉了CSRF的防御功能,但是这样是不安全的,那么怎么样才是正确的做法呢?

    整体来说,就是两个思路:

    1. 生成 csrfToken 保存在 HttpSession 或者 Cookie 中。
    2. 请求到来时,从请求中提取出来 csrfToken,和保存的 csrfToken 做比较,进而判断出当前请求是否合法。

    一、CSRF 参数生成

    首先,Spring Security 中提供了一个保存 csrf 参数的规范,就是 CsrfToken:

    public interface CsrfToken extends Serializable {
     String getHeaderName();
     String getParameterName();
     String getToken();
    }
    

    这里三个方法都好理解,前两个是获取 _csrf 参数的 key,第三个是获取 _csrf 参数的 value。

    CsrfToken 有两个实现类,如下:

     

    默认情况下使用的是 DefaultCsrfToken,我们来稍微看下 DefaultCsrfToken:

    public final class DefaultCsrfToken implements CsrfToken {
     private final String token;
     private final String parameterName;
     private final String headerName;
     public DefaultCsrfToken(String headerName, String parameterName, String token) {
      this.headerName = headerName;
      this.parameterName = parameterName;
      this.token = token;
     }
     public String getHeaderName() {
      return this.headerName;
     }
     public String getParameterName() {
      return this.parameterName;
     }
     public String getToken() {
      return this.token;
     }
    }
    

    这段实现很简单,几乎没有添加额外的方法,就是接口方法的实现。

    CsrfToken 相当于就是 _csrf 参数的载体。那么参数是如何生成和保存的呢?这涉及到另外一个类:

    public interface CsrfTokenRepository {
     CsrfToken generateToken(HttpServletRequest request);
     void saveToken(CsrfToken token, HttpServletRequest request,
       HttpServletResponse response);
     CsrfToken loadToken(HttpServletRequest request);
    } 
    

    这里三个方法:

    generateToken 方法就是 CsrfToken 的生成过程。
    saveToken 方法就是保存 CsrfToken。
    loadToken 则是如何加载 CsrfToken。
    CsrfTokenRepository 有四个实现类,其中两个:HttpSessionCsrfTokenRepository 和 CookieCsrfTokenRepository 的 HttpSessionCsrfTokenRepository 是默认的方案。

     

    我们先来看下 HttpSessionCsrfTokenRepository 的实现:

    public final class HttpSessionCsrfTokenRepository implements CsrfTokenRepository {
     private static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";
     private static final String DEFAULT_CSRF_HEADER_NAME = "X-CSRF-TOKEN";
     private static final String DEFAULT_CSRF_TOKEN_ATTR_NAME = HttpSessionCsrfTokenRepository.class
       .getName().concat(".CSRF_TOKEN");
     private String parameterName = DEFAULT_CSRF_PARAMETER_NAME;
     private String headerName = DEFAULT_CSRF_HEADER_NAME;
     private String sessionAttributeName = DEFAULT_CSRF_TOKEN_ATTR_NAME;
     public void saveToken(CsrfToken token, HttpServletRequest request,
       HttpServletResponse response) {
      if (token == null) {
       HttpSession session = request.getSession(false);
       if (session != null) {
        session.removeAttribute(this.sessionAttributeName);
       }
      }
      else {
       HttpSession session = request.getSession();
       session.setAttribute(this.sessionAttributeName, token);
      }
     }
     public CsrfToken loadToken(HttpServletRequest request) {
      HttpSession session = request.getSession(false);
      if (session == null) {
       return null;
      }
      return (CsrfToken) session.getAttribute(this.sessionAttributeName);
     }
     public CsrfToken generateToken(HttpServletRequest request) {
      return new DefaultCsrfToken(this.headerName, this.parameterName,
        createNewToken());
     }
     private String createNewToken() {
      return UUID.randomUUID().toString();
     }
    }
    

    这段源码其实也很好理解:

    saveToken 方法将 CsrfToken 保存在 HttpSession 中,将来再从 HttpSession 中取出和前端传来的参数做笔记。
    loadToken 方法当然就是从 HttpSession 中读取 CsrfToken 出来。
    generateToken 是生成 CsrfToken 的过程,可以看到,生成的默认载体就是 DefaultCsrfToken,而 CsrfToken 的值则通过 createNewToken 方法生成,是一个 UUID 字符串。
    在构造 DefaultCsrfToken 是还有两个参数 headerName 和 parameterName,这两个参数是前端保存参数的 key。

    这是默认的方案,适用于前后端不分的开发。

    如果想在前后端分离开发中使用,那就需要 CsrfTokenRepository 的另一个实现类 CookieCsrfTokenRepository ,代码如下:

    public final class CookieCsrfTokenRepository implements CsrfTokenRepository {
     static final String DEFAULT_CSRF_COOKIE_NAME = "XSRF-TOKEN";
     static final String DEFAULT_CSRF_PARAMETER_NAME = "_csrf";
     static final String DEFAULT_CSRF_HEADER_NAME = "X-XSRF-TOKEN";
     private String parameterName = DEFAULT_CSRF_PARAMETER_NAME;
     private String headerName = DEFAULT_CSRF_HEADER_NAME;
     private String cookieName = DEFAULT_CSRF_COOKIE_NAME;
     private boolean cookieHttpOnly = true;
     private String cookiePath;
     private String cookieDomain;
     public CookieCsrfTokenRepository() {
     }
     @Override
     public CsrfToken generateToken(HttpServletRequest request) {
      return new DefaultCsrfToken(this.headerName, this.parameterName,
        createNewToken());
     }
     @Override
     public void saveToken(CsrfToken token, HttpServletRequest request,
       HttpServletResponse response) {
      String tokenValue = token == null ? "" : token.getToken();
      Cookie cookie = new Cookie(this.cookieName, tokenValue);
      cookie.setSecure(request.isSecure());
      if (this.cookiePath != null && !this.cookiePath.isEmpty()) {
        cookie.setPath(this.cookiePath);
      } else {
        cookie.setPath(this.getRequestContext(request));
      }
      if (token == null) {
       cookie.setMaxAge(0);
      }
      else {
       cookie.setMaxAge(-1);
      }
      cookie.setHttpOnly(cookieHttpOnly);
      if (this.cookieDomain != null && !this.cookieDomain.isEmpty()) {
       cookie.setDomain(this.cookieDomain);
      }
    
      response.addCookie(cookie);
     }
     @Override
     public CsrfToken loadToken(HttpServletRequest request) {
      Cookie cookie = WebUtils.getCookie(request, this.cookieName);
      if (cookie == null) {
       return null;
      }
      String token = cookie.getValue();
      if (!StringUtils.hasLength(token)) {
       return null;
      }
      return new DefaultCsrfToken(this.headerName, this.parameterName, token);
     }
     public static CookieCsrfTokenRepository withHttpOnlyFalse() {
      CookieCsrfTokenRepository result = new CookieCsrfTokenRepository();
      result.setCookieHttpOnly(false);
      return result;
     }
     private String createNewToken() {
      return UUID.randomUUID().toString();
     }
    } 
    

    和 HttpSessionCsrfTokenRepository 相比,这里 _csrf 数据保存的时候,都保存到 cookie 中去了,当然读取的时候,也是从 cookie 中读取,其他地方则和 HttpSessionCsrfTokenRepository 是一样的。

    每次合法的身份验证之后, 都应当更换缓存中的 csrf-token, 并在相应头中置入新的 csrf-token. 这一过程受控于 CsrfAuthenticationStrategy 这个类, 它负责在执行认证请求之后, 删除旧的令牌, 生成新的. 确保每次请求之后, csrf-token 都得到更新.

    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);
    		}
    	}
    
    

    CsrfAuthentication#onAuthentication 的执行时机: SessionManagementFilter

    OK,这就是我们整个 _csrf 参数生成的过程。

    总结一下,就是生成一个 CsrfToken,这个 Token,本质上就是一个 UUID 字符串,然后将这个 Token 保存到 HttpSession 中,或者保存到 Cookie 中,待请求到来时,从 HttpSession 或者 Cookie 中取出来做校验。

    二、参数校验

    校验主要是通过 CsrfFilter 过滤器来进行,我们来看下核心的 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);
    }
    
    

    这个方法我来稍微解释下:

    首先调用 tokenRepository.loadToken 方法读取 CsrfToken 出来,这个 tokenRepository 就是你配置的 CsrfTokenRepository 实例,CsrfToken 存在 HttpSession 中,这里就从 HttpSession 中读取,CsrfToken 存在 Cookie 中,这里就从 Cookie 中读取。
    如果调用 tokenRepository.loadToken 方法没有加载到 CsrfToken,那说明这个请求可能是第一次发起,则调用 tokenRepository.generateToken 方法生成 CsrfToken ,并调用 tokenRepository.saveToken 方法保存 CsrfToken。
    大家注意,这里还调用 request.setAttribute 方法存了一些值进去,这就是默认情况下,我们通过 jsp 或者 thymeleaf 标签渲染 _csrf 的数据来源。
    requireCsrfProtectionMatcher.matches 方法则使用用来判断哪些请求方法需要做校验,默认情况下,"GET", "HEAD", "TRACE", "OPTIONS" 方法是不需要校验的。
    接下来获取请求中传递来的 CSRF 参数,先从请求头中获取,获取不到再从请求参数中获取。
    获取到请求传来的 csrf 参数之后,再和一开始加载到的 csrfToken 做比较,如果不同的话,就抛出异常。
    如此之后,就完成了整个校验工作了。

    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .ignoringAntMatchers("/authentication");
            .and()
            ...
        }
    }
    
    

    使用CookieCsrfTokenRepository生成CSRF Token放入cookie,并设置cookie的HttpOnly=false,允许js读取该cookie。

    使用ignoringAntMatchers开放一些不需要进行CSRF防护的访问路径,比如:登录授权。

    有小伙伴可能会说放在 Cookie 中不是又被黑客网站盗用了吗?其实不会的,大家注意如下两个问题:

    1. 黑客网站根本不知道你的 Cookie 里边存的啥,他也不需要知道,因为 CSRF 攻击是浏览器自动携带上 Cookie 中的数据的。
    2. 我们将服务端生成的随机数放在 Cookie 中,前端需要从 Cookie 中自己提取出来 _csrf 参数,然后拼接成参数传递给后端,单纯的将 Cookie 中的数据传到服务端是没用的。

    理解透了上面两点,你就会发现 _csrf 放在 Cookie 中是没有问题的,但是大家注意,配置的时候我们通过 withHttpOnlyFalse 方法获取了 CookieCsrfTokenRepository 的实例,该方法会设置 Cookie 中的 HttpOnly 属性为 false,也就是允许前端通过 js 操作 Cookie(否则你就没有办法获取到 _csrf)。

    配置完成后,重启项目,此时我们就发现返回的 Cookie 中多了一项:

     三、前端请求携带CSRF Token的方式

    我们生成了CSRF token保存在了cookies中,浏览器向服务端发送的HTTP请求,都要将CSRF token带上,服务端校验通过才能正确的响应。这个校验的过程并不需要我们自己写代码实现,Spring Security会自动处理。但是我们需要关注前端代码,如何正确的携带CSRF token。

    在thymeleaf模板中可以使用如下方式,在发送HTTP请求的时候携带CSRF Token。如果是前后端分离的应用,或者其他模板引擎,酌情从cookies中获取CSRF Toekn。

    Axios.interceptors.request.use(
      function(config) {
        // 在 post 请求前统一添加 X-CSRFToken 的 header 信息
        let cookie = document.cookie;
        if(cookie && config.method == 'post'){
          config.headers['X-CSRFToken'] = getCookie(cookie);
        }
        return config;
      },
      function(error) {
        // Do something with request error
        return Promise.reject(error);
      });
    
    

    csrf 攻击主要是借助了浏览器默认发送 Cookie 的这一机制,所以如果你的前端是 App、小程序之类的应用,不涉及浏览器应用的话,其实可以忽略这个问题,如果你的前端包含浏览器应用的话,这个问题就要认真考虑了。

    展开全文
  • CSRF 是什么 跨站请求伪造 知乎解答搬运: csrf是什么. Springboot CSRF Angular CSRF Angular官方文档: cross-site-request-forgery. 在跨站请求伪造(XSRF 或 CSFR)中,攻击者欺骗用户,让他们访问一个假冒页面...
  • 二、Laravel的CSRF防御过程 Laravel 会自动在用户 session (根据session_id 关联确认属于谁) 生成存放一个随机令牌(token)放在session中,并且如果使用 Laravel 的 {{form::open}} 会自动隐藏存在 csrf_token(),...
  • 主要介绍了spring security中的csrf防御机制原理解析(跨域请求伪造),本文通过实例代码详解的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
  • jsonp格式下的csrf防御方法
  • app里面还是要绑定CSRFProtectfrom flask_wtf import CSRFProtect # flask_wtf 已经提供CSRF防御手段CSRFProtect(app) # 绑定app登录页的js$(function () {$(‘#submit‘).click(function (event) {event....
  • 首先,确保在springSecurity.xml里没有把csrf关掉,即配置文件中不要出现以下代码:2.一般form表单3.对于Ajax和JSON请求4.上传文件,Multipart 1.首先,确保在springSecurity.xml里没有把csrf关掉,即配置文件中不要...
  • Spring Security默认就已经开启CSRF防御。 什么是CSRF CSRF 是浏览器跨站伪造请求,黑客可以诱导用户执行一些用户意想不到行为,它允许攻击者部分绕开 同源策略。 例如,当用户登录系统A后,用户可以修改自己的邮箱...
  • CSRF防御.docx

    2012-12-11 15:54:21
    CSRF是什么? CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attacksession riding,缩写为:CSRFXSRF。 二.CSRF可以做什么? 你这可以这么理解CSRF攻击:攻击者盗用了你的...
  • 原文 基于令牌的措施 这种防御方式是防御CSRF方式中最受欢迎和推荐的。它能够通过有状态(同步令牌模式)或无状态(基于加密或哈希的令牌模式)实现。...将CSRF防御添加到现有应用中单外部组件也非常建议。
  • csrf防御

    2019-05-25 18:00:43
    根据我的理解来对csrf 防御进行阐述(很可能有不对的地方,很抱歉,希望指正) 1.当我们请求一个包含表单提交的页面时,我们就可以在 session 中 设置 key 为用户唯一标识, value为相对唯一未加密文本。 2.服务器端...
  • Spring Security 自定义登录页并开启CSRF防御,http.csrf()源码分析
  • Django中CSRF防御全过程解析以及中间件作用机制

    千次阅读 多人点赞 2019-05-21 18:49:23
    XSS和CSRF攻击的基础原理这里就不介绍了,之前写了一篇文章单独介绍的很详细了,传送门,这里我们直接以Django为分析对象,分析中间件csrf生成原理以及防范Token如何运作的。 CSRF中间件 官方文档介绍的也是表面,...
  • 针对Breach攻击的CSRF防御模块的设计与实现,刘赟,郭燕慧,随着越来越多的真实Web世界的CSRF攻击事件的发生,更多的安全工程师开始重视起这个原本被忽略的攻击方式,新出现的BREACH攻击也对目�
  • flask中的csrf防御

    2020-03-25 00:45:03
    flask中的csrf防御机制 两种方式: 方式一: 1、在客户端向后端请求界面数据的时候,后端会往响应中的 cookie 中设置 csrf_token 的值 2、在 Form 表单中添加一个隐藏的的字段,值也是 csrf_token 3、在用户点击提交...
  • 基于MD5的CSRF防御模块的设计与实现 系统安全 金融安全 威胁情报 web安全 安全运营
  • Django框架学习16--csrf防御机制及原理

    千次阅读 2019-12-20 15:03:18
    csrf攻击说明 1.用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A; 2.在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A; 3.用户未...
  • 4.4CSRF防御

    2020-05-28 17:02:49
    4.4 CSRF防御 二次确认 在调用某些功能时进行二次验证, 如:删除用户时,产生一个提示对话框,提示“确定删除用户吗?”。 转账操作时,要求用户输入二次密码,设置验证码,在进行敏感操作时输入验证码。 当二次验证后,...
  • CSRF防御之token认证

    万次阅读 2019-06-24 21:39:01
    一、CSRF是什么? CSRF(Cross-site request forgery),中文名称:跨站请求伪造。攻击者盗用你的身份,以你的名义发送恶意请求。...三、防御CSRF的策略:token认证 1、token验证方法 CSRF 攻击之...
  • CSRF防御实例记录

    2019-10-28 18:16:57
    static final String CSRF_PARAM_NAME = "CSRFToken"; /** * The location on the session which stores the token */ public static final String CSRF_TOKEN_FOR_SESSION_ATTR_NAME = CSRFTokenManager....
  • CSRF防御实施方案

    2021-02-06 10:43:22
    对服务端响应的特殊错误码(0008)做相应CSRF风险提示。 token创建 读取SEED值。SEED可以用sessionId,也可以用固定的值。SEED建议24位,不够用0后补位。 每次发送服务端请求前,基于SEED和当前客户端系统时间...
  • 58金融的CSRF防御实践

    2020-12-08 00:00:00
    导读防范CSRF攻击对于互联网企业来说意义重大,本文结合58金融的实践场景,旨在帮助大家共同提高nodejs服务的安全性。背景Web端的跨站点请求伪造(Cross Site Reques...
  • 在前端模版的JavaScript代码...this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrftoken); } } }); 上面代码的作用就是让ajax的POST方法带上CSRF需要的令牌,它依赖Jquery库,必须提前加载Jquery。 举例:
  • 绕过CSRF防御

    2021-01-26 20:51:50
    CSRF漏洞很容易就可以被发现并利用。一眼看去很多站点好像在这方面都做得不错:当你检查针对敏感操作的请求时,他们往往会实施CSRF保护。...今天我们讨论一些我如何绕过CSRF防御措施的技术。所有的CSRF不管哪种CS...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 13,225
精华内容 5,290
关键字:

csrf防御

友情链接: Starting_point.zip