上文总结了csrf攻击以及一些常用的防护方式,csrf全称Cross-site request forgery(跨站请求伪造),是一类利用信任用户已经获取的注册凭证,绕过后台用户验证,向被攻击网站发送未被用户授权的跨站请求以对被攻击网站执行某项操作的一种恶意攻击方式。

  上面的定义比较抽象,我们先来举一个简单的例子来详细解释一下csrf攻击,帮助理解。

  假设你通过电脑登录银行网站进行转账,一般这类转账页面其实是一个form表单,点击转账其实就是提交表单,向后台发起http请求,请求的格式大概像下面这个样子:

POST /transfer HTTP/1.1
Host: xxx.bank.com
Cookie: JSESSIONID=randomid; Domain=xxx.bank.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded amount=100.00&routingNumber=1234&account=8888

  好了,现在给自己的账户转完账了,但是这时你一般不会立马退出银行网站的登录,你可能会紧接着去上网浏览别的网页,碰巧你上网的时候看到一些很吸引人眼球的广告(比如在家兼职轻松月入上万。。。之类的),你点击了一下,但是发现什么也没有,也许你会关掉这个网页,以为什么都没有发生。但是后台可能已经发生了一系列的事情,如果这是个钓鱼网站,并且刚才你点击的页面恰好又包含一个form表单,如下所示:

<form action="https://xxx.bank.com/transfer" method="post">
<input type="hidden"
name="amount"
value="100.00"/>
<input type="hidden"
name="routingNumber"
value="evilsRoutingNumber"/>
<input type="hidden"
name="account"
value="evilsAccountNumber"/>
<input type="submit"
value="Win Money!"/>
</form>

  这里只要你点击网页便会自动提交表单,导致你向一个陌生账户转账100元(这些都可通过js实现自动化),而且是未经过你的授权的情况下,这就是csrf的攻击方式,虽然其不知道你的登录信息,但是其利用浏览器自身的机制来冒充用户绕过后台用户验证从而发起攻击。

  csrf是一种常见的web攻击方式,一些现有的安全框架中都对该攻击的防护提供了支持,比如spring security,从4.0开始,默认就会启用CSRF保护,会针对PATCH,POST,PUT和DELETE方法进行防护。本文会结合spring security提供的防护方法,并结合其源码来学习一下其内部防护原理,本文涉及到的Spring Security源码版本为5.1.5。

  本文目录如下:

  使用Spring Security防护CSRF攻击

  Spring Security的CSRF防护原理

  总结

1. 使用Spring Security防护CSRF攻击

  通过Spring Security来防护CSRF攻击需要做哪些配置呢,总结如下:

  • 使用合适的HTTP请求方式
  • 配置CSRF保护
  • 使用CSRF Token

1.1 使用合适的HTTP请求方式

  第一步是确保要保护的网站暴露的接口使用合适的HTTP请求方式,就是在还未开启Security的CSRF之前需要确保所有的接口都只支持使用PATCH、POST、PUT、DELETE这四种请求方式之一来修改后端数据。

  这并不是Spring Security在防护CSRF攻击方面的自身限制,而是合理防护CSRF攻击所必须做的,原因是通过GET的方式传递私有数据容易导致其泄露,使用POST来传递敏感数据更合理。

1.2 配置CSRF保护

  下一步就是将Spring Security引入你的后台应用中。有些框架通过让用户session失效来处理无效的CSRF Token,但是这种方式是有问题的,取而代之,Spring Security默认返回一个403的HTTP状态码来拒绝无效访问,可以通过配置AccessDeniedHandler来实现自己的拒绝逻辑。

  如果项目中是采用的XML配置,则必须显示的使用<csrf>标签元素来开启CSRF防护,详见<csrf>

  通过Java配置的方式则会默认开启CSRF防护,如果希望禁用这一功能,则需要手动配置,见下面的示例,更详细的配置可以参考csrf()方法的官方文档。

@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends
WebSecurityConfigurerAdapter { @Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable();
}
}

1.3 使用CSRF Token

  接下来就是在每次请求的时候带上一个CSRF Token,根据请求的方式不同会有不同的方式:

1.3.1 Form表单提交

  通过表单提交会将CSRF Token附在Http请求的_csrf属性中,后台接口从请求中获取token,如下是一个示例(JSP):

<c:url var="logoutUrl" value="/logout"/>
<form action="${logoutUrl}"
method="post">
<input type="submit"
value="Log out" />
<input type="hidden"
name="${_csrf.parameterName}"
value="${_csrf.token}"/>
</form>

  其实就是后台在渲染页面时先生成一个CSRF Token,放到表单中;然后在用户提交表单时就会附带上这个CSRF Token,后台将其取出来并进行校验,不一致则拒绝这次请求。这里因为这Token是后台生成的,这对于第三方网站是获取不到的,通过这种方式实现防护。

1.3.2 Ajax和JSON请求

  如果是使用的JSON,则不需要将CSRF Token以HTTP参数的形式提交,而是放在HTTP请求头中。典型的做法是将CSRF Token包含在在页面的元标签中。如下是一个JSP的例子:

<html>
<head>
<meta name="_csrf" content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" content="${_csrf.headerName}"/>
<!-- ... -->
</head>
<!-- ... -->

  然后在所有的Ajax请求中需要带上CSRF Token,如下是jQuery中的实现:

$(function () {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
});

  到这里所有的配置都已经好了,包括接口调用方式的设计、框架的配置、前端页面的配置,前文中讲了一系列的防护方式,Spring Security又是采用的什么方式呢,最直接的方式就是看源码了。

2. Spring Security的CSRF防护原理

  Spring Security是基于Filter(过滤器)来实现其安全功能的,关于CSRF防护的主要逻辑是在CsrfFilter这个过滤器中的,继承自OncePerRequestFilter,并且重写了doFilterInternal方法:

    protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
     // 通过tokenRepository从request中获取csrf token
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
final boolean missingToken = csrfToken == null;
     // 如果未获取到token则新生成token并保存
if (missingToken) {
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
     // 判断是否需要进行csrf token校验
if (!this.requireCsrfProtectionMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
     // 获取前端传过来的实际token
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
     // 校验两个token是否相等
if (!csrfToken.getToken().equals(actualToken)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Invalid CSRF token found for "
+ UrlUtils.buildFullRequestUrl(request));
}
       // 如果是token缺失导致,则抛出MissingCsrfTokenException异常
if (missingToken) {
this.accessDeniedHandler.handle(request, response,
new MissingCsrfTokenException(actualToken));
}
       // 如果不是同一个token则抛出InvalidCsrfTokenException异常
else {
this.accessDeniedHandler.handle(request, response,
new InvalidCsrfTokenException(csrfToken, actualToken));
}
return;
}
     // 执行下一个过滤器
filterChain.doFilter(request, response);
}

  整个流程还是很清晰的,我们总结一下:

  • 先通过tokenRepository从request中获取csrf token;
  • 如果未获取到token则新生成token并保存;
  • 判断是否需要进行csrf token校验,不需要则直接执行下一个过滤器;
  • 调用request的getHeader()方法或者getParameter()方法获取前端传过来的实际token;
  • 校验两个token是否相等,不相等则抛出异常,相等则校验通过,执行下一个过滤器;

  可以知道,Spring Security是借助CSRF Token来实现防护的,上文我们讲到,通过token的方式可以选择cookie来存储也可以选择session的方式,那Spring Security提供了什么方式呢,答案就在获取token的tokenRepository中,我们看一下,这个tokenRepository类型是CsrfTokenRepository(这是一个接口),Spring Security提供了三种实现,分别是HttpSessionCsrfTokenRepository、CookieCsrfTokenRepository、LazyCsrfTokenRepository,我们着重看一下前两者,顾名思义,一个是通过session,而另一个则是通过cookie,我们再分别看一下其各自实现的loadToken()方法,验证一下。

    // CookieCsrfTokenRepository中的实现
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);
} // HttpSessionCsrfTokenRepository中的实现
public CsrfToken loadToken(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return null;
}
return (CsrfToken) session.getAttribute(this.sessionAttributeName);
}

  到这里我们已经很清楚了,Spring Security提供多种保存token的策略,既可以保存在cookie中,也可以保存在session中,这个可以手动指定。所以前文说到的两个关于token的防护方式,Spring Security都支持。既然到这里了,我们就再看一下Spring Security是如何生成和保存token的,这里仅以CookieCsrfTokenRepository的实现为例:

    // 生成token
public CsrfToken generateToken(HttpServletRequest request) {
return new DefaultCsrfToken(this.headerName, this.parameterName,
createNewToken());
} private String createNewToken() {
return UUID.randomUUID().toString();
} // 保存token
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);
}
if (cookieHttpOnly && setHttpOnlyMethod != null) {
ReflectionUtils.invokeMethod(setHttpOnlyMethod, cookie, Boolean.TRUE);
} response.addCookie(cookie);
}

  可以看到,生成的token其实本质就是一个uuid,而保存则是保存在cookie中,涉及到cookie操作,其中有很多细节,本文就不详述了。

3. 总结

  本文先解释了一个csrf攻击的基本例子,然后介绍了使用Spring Security来防护csrf攻击所需要的配置,最后再从Spring Security源码的角度学习了一下其是如何实现csrf防护的,基本原理还是通过token来实现,具体可以借助于cookie和session的方式来实现。

注:本文涉及到的源码均来自Spring Security 5.1.5。

参考文献:

Cross Site Request Forgery (CSRF)

Spring Security Architecture

Java 安全之:csrf防护实战分析的更多相关文章

  1. Java日志框架解析及实战分析

    转载自: https://zhuanlan.zhihu.com/p/24272450 https://zhuanlan.zhihu.com/p/24275518 作为Java程序员,幸运的是,Java ...

  2. Java互联网架构-Mysql分库分表订单生成系统实战分析

    概述 分库分表的必要性 首先我们来了解一下为什么要做分库分表.在我们的业务(web应用)中,关系型数据库本身比较容易成为系统性能瓶颈,单机存储容量.连接数.处理能力等都很有限,数据库本身的“有状态性” ...

  3. JAVA企业级应用服务器之TOMCAT实战

    JAVA企业级应用服务器之TOMCAT实战 链接:https://pan.baidu.com/s/1c6pZjLeMQqc9t-OXvUM66w 提取码:uwak 复制这段内容后打开百度网盘手机App ...

  4. CSRF漏洞实战靶场笔记

    记录下自己写的CSRF漏洞靶场的write up,包括了大部分的CSRF实战场景,做个笔记. 0x01 无防护GET类型csrf(伪造添加成员请求) 这一关没有任何csrf访问措施 首先我们登录tes ...

  5. Linux下java进程CPU占用率高分析方法

    Linux下java进程CPU占用率高分析方法 在工作当中,肯定会遇到由代码所导致的高CPU耗用以及内存溢出的情况.这种情况发生时,我们怎么去找出原因并解决. 一般解决方法是通过top命令找出消耗资源 ...

  6. java String.split()函数的用法分析

    java String.split()函数的用法分析 栏目:Java基础 作者:admin 日期:2015-04-06 评论:0 点击: 3,195 次 在java.lang包中有String.spl ...

  7. elk实战分析nginx日志文档

    elk实战分析nginx日志文档 架构: kibana <--- es-cluster <--- logstash <--- filebeat 环境准备:192.168.3.1 no ...

  8. [转]使用Java Mission Control进行内存分配分析

    jdk7u40自带了一个非常好用的工具,就是Java Mission Control.JRockit Misson Control用户应该会对mission control的很多功能十分熟悉,JRoc ...

  9. 最牛「CSRF防护」,带你进入大虾们的圈子!

    简单理解 CSRF 什么是 CSRF? CSRF,通常称为跨站请求伪造,英文名 Cross-site request forgery 缩写 CSRF,是一种对网站的恶意攻击.一个跨站请求伪造攻击迫使登 ...

随机推荐

  1. GitLab-CI 来自动创建 Docker 镜像

    1.what is gitlab-ci docker image CI/CD 自动化集成,自动化部署.简单的说就是把代码提交到gitlab管理的同时部署到指定的server,打成docker imag ...

  2. git 必看,各种撤销操作

    场景概念说明 首先说明一个概念, git是一个分布式的版本控制工具,分布式即 git 管理的项目是有多个大致平等的仓库的.通过一个例子来说明这个东西. 举一个最简单的使用场景: 你在github 建立 ...

  3. PyQt4 在Windows下安装

    快来加入群[python爬虫交流群](群号570070796),发现精彩内容.     首先在网上下载sip文件下载完之后解压, 在Windows的开始菜单栏中进入sip的解压目录下:   在目录下面 ...

  4. MapReduce 编程模型 & WordCount 示例

    学习大数据接触到的第一个编程思想 MapReduce.   前言 之前在学习大数据的时候,很多东西很零散的做了一些笔记,但是都没有好好去整理它们,这篇文章也是对之前的笔记的整理,或者叫输出吧.一来是加 ...

  5. Charles PC端和手机端抓取HTTP和HTTPS协议请求、HTTPS通用抓包规则

    一:HTTP和HTTPS的区别 HTTP是超文本传输协议,被用在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,因此HTTP协议不适合传输一些敏感信息, ...

  6. js页面3秒自动跳转

    如何让当前页面3秒以后自动跳转到其他页面?JS页面自动跳转 想实现登陆后3秒自动跳转到某页的功能,在网上搜了一下,供以后使用 1.<script   language= "javasc ...

  7. PHP后门***详解

    说起php后门***我就心有愉季啊前不久一个站就因不小心给人注入了然后写入了小***这样结果大家知道的我就不说了下面我来给大家收集了各种php后门***做法大家可参考. php后门***对大家来说一点 ...

  8. 浅谈Ceph纠删码

    目  录第1章 引言 1.1 文档说明 1.2 参考文档 第2章 纠删码概念和原理 2.1 概念 2.2 原理 第3章 CEPH纠删码介绍 3.1 CEPH纠删码用途 3.2 CEPH纠删码库 3.3 ...

  9. ccf 201903-3 损坏的RAID5

    9月份考ccf,暑假打算做一些往年的真题... 这个题,一开始真是把我给看晕了 传说中的大模拟,果然不简单QAQ 首先读懂题目就是一个大难点,特别是对于我这种题目一长就看不进去的人来说 读懂题目之后, ...

  10. .netcore持续集成测试篇之Xunit数据驱动测试一

    系列目录 Nunit里提供了丰富的数据测试功能,虽然Xunit里提供的比较少,但是也能满足很多场景下使用了,如果数据场景非常复杂,Nunit和Xunit都是无法胜任的,有不少测试者选择自己编写一个数据 ...