同步令牌模式防范CSRF跨站请求伪造攻击

什么是“跨渣请求伪造”呢?这是信息安全领域的一个名词,译自英文“Cross Site Request Forgery”。

百度百科上介绍的很简单却很明了,大家可以看一下,我这里配合一些代码稍微多说一点。

假设我们要在银行网站上给老妈转100块钱,毕竟毕业这么多年了也没给过家里钱(虽然你认为他们都在赚钱不需要你给,况且你自己现在赚钱刚好可以经济独立,不过实际上爹妈还是很希望你能支援家里的)。这个转账的HTTP请求类似于这样:

POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876

 你现在已经登录银行的网站了(你说你用的客户端?那当然没有这个危险性了,所以银行才都推荐U盾的),然后你新开了个标签页,进入一个“危机四伏”的网站(你傻啊,为什么要这样做。其实当别人没骗的时候我们都可以马后炮,但是没有足够的防范意识,任何时候我们被骗都是在鼓里)。

这个网站有一个链接,代码类似于这样:

<form action="https://bank.example.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="点我拿红包!"/>
</form>

 除了一个“拿红包”的按钮之外,其他都是隐藏域。一看抢红包了,赶紧抢吧!这个表单提交后,和上面的请求是完全一样的。尽管这个网站没有你的cookie信息,可是你提交的时候cookie照样会被发送,所以它完全不需要拿到你的cookie。

你说:好像只要我足够小心,不去点击按钮就行了。当然不是,因为给你个按钮让你点是貌似愚蠢的做法,这个网站打开的同时,它的javascript代码说不定就不停的发生那个请求了。而你完全不知道。

为什么会这样?

因为接收action的服务器并不知道请求是跨站的,跨不跨站对于服务器来说没什么两样。

知道了这一点,我们的解决方法也就应运而生了:增加点东西让其他站点提供不了(因为只用cookie是不够的),这样服务器去验证这个域不就可以了吗。

一种方案是同步令牌模式。你的每一次请求,除了session数据外,都会提供服务器经response返回的随机串作为一个单独的http参数。提交后服务器会验证这个随机串。而其他网站是拿不到这个随机串的。

与之前的请求相比,只多了一个参数_csrf:

POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid; Domain=bank.example.com; Secure; HttpOnly
Content-Type: application/x-www-form-urlencoded

amount=100.00&routingNumber=1234&account=9876&_csrf=<secure-random>

对于经过浏览器进行访问和发送接收请求的场景,防范CSRF攻击都有必要。 

 Spring Secure是怎么防范CSRF攻击呢?

首先,我们使用spring secure应该保证action的HTTP动词是合适的,不应该使用GET请求处理编辑性活动。使用POST要好得多。有的框架检测到token(就是上面提到的随机串)不合法马上就将用户登出要求重新登录。而Spring是产生一个403状态。

spring默认是开启 csrf防范的(如果使用命名空间需要开发者显示开启),如果想关闭,可以使用disable()方法:

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

 最后,要保证在除了GET外的请求中都包含_csrf域。不然,连登出都会失败(咦,登出不是GET吗?所以最好用POST)。

相关推荐