Play 2.6 抵御CSRF

抵御跨站伪造请求

https://playframework.com/documentation/2.6.x/JavaCsrf

CSRF是一个安全漏洞,攻击者通过受害者的浏览器在会话期间发起一个请求。由于每一个请求都会带有session token,如果攻击者能够迫使被害者浏览器以自己的身份发出请求,那么也能以用户的名义发出请求。

建议你了解以下CSRF,了解一下什么是攻击向量什么不是,可以通过这里熟悉CSRF

对于什么是安全的请求没有一个简单的答案,因为目前还没有一个明确的规则说明插件和未来的扩展可以做什么( the reason for this is that there is no clear specification as to what is allowable from plugins and future extensions to specifications. )历史上,浏览器插件和扩展项会将规则放宽,认为框架是可以信赖的,这将CSRF威胁带到了许多应用中,然后由框架来修复这些漏洞。基于这个原因,Play采取了保守的策略,但是当允许你配置什么时候进行检查。默认情况下,当以下条件成立时Play会采取CSRF检测
- GET、HEAD、OPTIONS请求
- 请求中包了一个或多个Cookier或Authorization头
- CORS 过滤器没有被配置为相信请求origin

Note:如果你是用基于浏览器的认证而不是cookies或者HTTP认证,比如NTLM或者客户端证书。那么你必须设置play.filters.csrf.header.protectHeaders = null,或者将在protectHeaders中使用的验证头部包含进来
Play的CSRF防御

Play提供多种方法来验证一个请求是否是CSRF。首要的机制是一个CSRF token。在提交表单或者query string都会带有这个Token,也保存在用户会话中。Play会验证双方的token是否一致。

为了简单的保护非浏览器请求,Play仅检查带有cookies的请求。如果通过Ajax来创建请求,你可以将CSRF token放到HTML页面中,然后通过Csrf-Token头降到请求中。

另外,可以通过设置play.filters.csrf.header.bypassHeaders来匹配普通的头部,一般规则如下:
- 如果有一个X-Requested-With头,Play会认为这个请求安全。X-Requested-With是很多常用js库添加的。
- 如果一个Csrf-Token头部的值为nocheck,或者有一个合法的CSRF token,则认为请求安全
配置如下

play.filters.csrf.header.bypassHeaders {
  X-Requested-With = "*"
  Csrf-Token = "nocheck"
}

使用如上配置的时候需要小心, as historically browser plugins have undermined this type of CSRF defence.

相信CORS请求

默认情况下,如果在CSRF过滤器前有一个CORS过滤器,CSRF过滤器会让CORS请求通过。如果想要禁用这,可以配置play.filters.csrf.bypassCorsTrustedOrigins = false.

使用全局的CSRF过滤器

Note: 从Play2.6开始,CSRF过滤器会是Play的默认过滤器之一。

Play提供了一个全局的CSRF过滤器来处理所有的请求,这是添加CSRF防御最简单的方法。可以通过以下方式手动添加到application.conf中

play.filters.enabled += "play.filters.csrf.CSRFFilter"

对于一些特殊路偶也可以禁用过滤,在routes文件中添加`nocsrf标签

+ nocsrf
POST  /api/new              controllers.Api.newThing()
获取当前的token

可以通过CSRF.getTOken方法来获取,这个方法需要RequestHeader参数,可以在Http.Context.current() 通过context.request()来获取

Optional<CSRF.Token> token = CSRF.getToken(request());
Note: 如果CSRF过滤器已经安装,当cokkie只被HTTP使用(也就说说JS无法获取)Play会试着避免生成token。当使用strick body发送响应时,Play会跳过添加token的过程除非CSRF.getToken。这在一些不需要CSRF token的相应中可以显著提高效率。如果cookie没有被配置为HttpOnly,Play会认为J你希望JS能够获取到cookie信息。 Note:如果你通过CompletionStage 来获得模版并得到了一个There is no HTTP Context的错误,那么你需要添加了一个HttpExecutionContext.current(),详见https://playframework.com/documentation/2.6.x/JavaAsync

为了给表单增加CSRF token,Play提供了一下模板工具,首先添加到action URL的查询字符串中。

@import helper._

@form(CSRF(scalaguide.forms.csrf.routes.ItemsController.save())) {
    ...
}

表单大致是这个样子:

<form method="POST" action="/items?csrfToken=1234567890abcdef">
   ...
</form>

如果不想在查询字符串中获取token,Play也支持提供一个helper在form中添加一个隐藏域来存放token

@form(scalaguide.forms.csrf.routes.ItemsController.save()) {
    @CSRF.formField
    ...
}
<form method="POST" action="/items">
   <input type="hidden" name="csrfToken" value="1234567890abcdef"/>
   ...
</form>
为会话添加CSRF token

为了保证表单中CSRF token可用且能反给客户端,如果新来的请求中token不可用,全局的过滤器会为所有获取HTML的GET请求生成一个新的token。

为一个action添加CSRF过滤器

有些情景下全局的CSRF过滤器并不适用,比如有一些应用也许希望通过一些跨源的表单请求(for example in situations where an application might want to allow some cross origin form posts.)一些不基于会话的标准,OpenID2.0,要求跨站的表单请求,或者提供RPC调用的表单提交。

在这些场景中,Play提供了两个action可以组合到我们的action中。

第一个是 play.filters.csrf.RequireCSRFCheck ,他会执行一个CSRF检查。这个action需要被添加到所有接收会话验证的POST表单提交的action中。

@RequireCSRFCheck
public Result save() {
    // Handle body
    return ok();
}

第二个是 play.filters.csrf.AddCSRFToken,如果接收的请求没有token就会生成一个。他需要被添加到所有render forms的action中

@AddCSRFToken
public Result get() {
    return ok(CSRF.getToken(request()).map(CSRF.Token::value).orElse("no token"));
}

CSRF可选配置

CSRF的所以配置可以在reference.conf 中找到。
- play.filters.csrf.token.name-用在会话和请求体/查询字符串中的名字
- play.filters.csrf.cookie.name- 如果配置了,Play会在cookie中根据配置的名字保存token,会话中将不再保存
- play.filters.csrf.cookie.secure- 如果上一项配置了,CSRF cookie是否需要安全flag set。 默认使用play.http.session.secure的值
- play.filters.csrf.body.bufferSize - 为了在body之外读取token,Play需要对bodu进行缓冲并根据情况进行解析。这一项设置了buffer的最大值,默认为00K
- play.filters.csrf.token.sign - Play是否使用签名的CSRF token。签名的CSRF token保证token的值在每次请求时都是随机的,因此可以抵御BREACH style攻击。

测试CSRF

在功能测试中,如果你为一个模板返回了CSRF token,你需要使这个token可用。可以在一个play.mvc.Http.RequestBuilder实例上调用play.api.test.CSRFTokenHelper.addCSRFToken

Http.RequestBuilder request = new Http.RequestBuilder()
        .method(POST)
        .uri("/xx/Kiwi");

request = CSRFTokenHelper.addCSRFToken(request);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值