CSRF(Cross-site request forgery,中文为跨站请求伪造)是一种利用网站可信用户的权限去执行未受权的命令的一种恶意攻击。经过假装可信用户的请求来利用信任该用户的网站,这种攻击方式虽然不是很流行,可是却难以防范,其危害也不比其余安全漏洞小。javascript
本文将简要介绍CSRF产生的缘由以及利用方式,而后对如何避免这种攻击方式提供一些可供参考的方案,但愿广大程序猿们都可以对这种攻击方式有所了解,避免本身开发的应用被别人利用。php
CSRF也称做one-click attack或者session riding,其简写有时候也会使用XSRF。html
[TOC]前端
本文将会持续修正和更新,最新内容请参考个人 GITHUB 上的 程序猿成长计划 项目,欢迎 Star,更多精彩内容请 follow me。java
简单点说,CSRF攻击就是 攻击者利用受害者的身份,以受害者的名义发送恶意请求。与XSS(Cross-site scripting,跨站脚本攻击)不一样的是,XSS的目的是获取用户的身份信息,攻击者窃取到的是用户的身份(session/cookie),而CSRF则是利用用户当前的身份去作一些未通过受权的操做。jquery
CSRF攻击最先在2001年被发现,因为它的请求是从用户的IP地址发起的,所以在服务器上的web日志中可能没法检测到是否受到了CSRF攻击,正是因为它的这种隐蔽性,很长时间以来都没有被公开的报告出来,直到2007年才真正的被人们所重视。git
CSRF能够盗用受害者的身份,完成受害者在web浏览器有权限进行的任何操做,想一想吧,能作的事情太多了。github
以你的名义发送诈骗邮件,消息web
用你的帐号购买商品ajax
用你的名义完成虚拟货币转帐
泄露我的隐私
...
要完成一个CSRF攻击,必须具有如下几个条件:
受害者已经登陆到了目标网站(你的网站)而且没有退出
受害者有意或者无心的访问了攻击者发布的页面或者连接地址
(图片来自网络,出处不明,百度来的?)
整个步骤大体是这个样子的:
用户小明在你的网站A上面登陆了,A返回了一个session ID(使用cookie存储)
小明的浏览器保持着在A网站的登陆状态,事实上几乎全部的网站都是这样作的,通常至少是用户关闭浏览器以前用户的会话是不会结束的
攻击者小强给小明发送了一个连接地址,小明打开了这个地址,查看了网页的内容
小明在打开这个地址的时候,这个页面已经自动的对网站A发送了一个请求,这时候由于A网站没有退出,所以只要请求的地址是A的就会携带A的cookie信息,也就是使用A与小明之间的会话
这时候A网站确定是不知道这个请求实际上是小强伪造的网页上发送的,而是误觉得小明就是要这样操做,这样小强就能够随意的更改小明在A上的信息,以小明的身份在A网站上进行操做
利用CSRF攻击,主要包含两种方式,一种是基于GET请求方式的利用,另外一种是基于POST请求方式的利用。
使用GET请求方式的利用是最简单的一种利用方式,其隐患的来源主要是因为在开发系统的时候没有按照HTTP动词的正确使用方式来使用形成的。对于GET请求来讲,它所发起的请求应该是只读的,不容许对网站的任何内容进行修改。
可是事实上并非如此,不少网站在开发的时候,研发人员错误的认为GET/POST的使用区别仅仅是在于发送请求的数据是在Body中仍是在请求地址中,以及请求内容的大小不一样。对于一些危险的操做好比删除文章,用户受权等容许使用GET方式发送请求,在请求参数中加上文章或者用户的ID,这样就形成了只要请求地址被调用,数据就会产生修改。
如今假设攻击者(用户ID=121)想将本身的身份添加为网站的管理员,他在网站A上面发了一个帖子,里面包含一张图片,其地址为http://a.com/user/grant_super_user/121
<img src="http://a.com/user/grant_super_user/121" />
设想管理员看到这个帖子的时候,这个图片确定会自动加载显示的。因而在管理员不知情的状况下,一个赋予用户管理员权限的操做已经悄悄的以他的身份执行了。这时候攻击者121就获取到了网站的管理员权限。
相对于GET方式的利用,POST方式的利用更加复杂一些,难度也大了一些。攻击者须要伪造一个可以自动提交的表单来发送POST请求。
<script> $(function() { $('#CSRF_forCSRFm').trigger('submit'); }); </script> <form action="http://a.com/user/grant_super_user" id="CSRF_form" method="post"> <input name="uid" value="121" type="hidden"> </form>
只要想办法实现用户访问的时候自动提交表单就能够了。
防范CSRF攻击,其实本质就是要求网站可以识别出哪些请求是非正经常使用户主动发起的。这就要求咱们在请求中嵌入一些额外的受权数据,让网站服务器可以区分出这些未受权的请求,好比说在请求参数中添加一个字段,这个字段的值从登陆用户的Cookie或者页面中获取的(这个字段的值必须对每一个用户来讲是随机的,不能有规律可循)。攻击者伪造请求的时候是没法获取页面中与登陆用户有关的一个随机值或者用户当前cookie中的内容的,所以就能够避免这种攻击。
令牌同步模式(Synchronizer token pattern,简称STP)是在用户请求的页面中的全部表单中嵌入一个token,在服务端验证这个token的技术。token能够是任意的内容,可是必定要保证没法被攻击者猜想到或者查询到。攻击者在请求中没法使用正确的token,所以能够判断出未受权的请求。
对于使用Js做为主要交互技术的网站,将CSRF的token写入到cookie中
Set-Cookie: CSRF-token=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql; expires=Thu, 23-Jul-2015 10:25:33 GMT; Max-Age=31449600; Path=/
而后使用javascript读取token的值,在发送http请求的时候将其做为请求的header
X-CSRF-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql
最后服务器验证请求头中的token是否合法。
使用验证码能够杜绝CSRF攻击,可是这种方式要求每一个请求都输入一个验证码,显然没有哪一个网站愿意使用这种粗暴的方式,用户体验太差,用户会疯掉的。
首先在index.php中,建立一个表单,在表单中,咱们将session中存储的token放入到隐藏域,这样,表单提交的时候token会随表单一块儿提交
<?php $token = sha1(uniqid(rand(), true)); $_SESSION['token'] = $token; ?> <form action="buy.php" method="post"> <input type="hidden" name="token" value="<?=$token; ?>" /> ... 表单内容 </form>
在服务端校验请求参数的buy.php
中,对表单提交过来的token与session中存储的token进行比对,若是一致说明token是有效的
<?php if ($_POST['token'] != $_SESSION['token']) { // TOKEN无效 throw new \Exception('Token无效,请求为伪造请求'); } // TOKEN有效,表单内容处理
对于攻击者来讲,在伪造请求的时候是没法获取到用户页面中的这个token
值的,所以就能够识别出其建立的伪造请求。
在Laravel框架中,使用了VerifyCSRFToken
这个中间件来防范CSRF攻击。
在页面的表单中使用{{ CSRF_field() }}
来生成token,该函数会在表单中添加一个名为_token
的隐藏域,该隐藏域的值为Laravel生成的token,Laravel使用随机生成的40个字符做为防范CSRF攻击的token。
$this->put('_token', Str::random(40));
若是请求是ajax异步请求,能够在meta
标签中添加token
<meta name="CSRF-token" content="{{ CSRF_token() }}">
使用jquery
做为前端的框架时候,能够经过如下配置将该值添加到全部的异步请求头中
$.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="CSRF-token"]').attr('content') } });
在启用session的时候,Laravel会生成一个名为_token
的值存储到session中。而使用前面两种方式在页面中加入的token就是使用的这一个值。在用户请求到来时,VerifyCSRFToken
中间件会对符合条件的请求进行CSRF检查
if ( $this->isReading($request) || $this->runningUnitTests() || $this->shouldPassThrough($request) || $this->tokensMatch($request) ) { return $this->addCookieToResponse($request, $next($request)); } throw new TokenMismatchException;
在if
语句中有四个条件,只要任何一个条件结果为true
则任何该请求是合法的,不然就会抛出TokenMismatchException
异常,告诉用户请求不合法,存在CSRF攻击。
第一个条件$this->isReading($request)
用来检查请求是否会对数据产生修改
protected function isReading($request) { return in_array($request->method(), ['HEAD', 'GET', 'OPTIONS']); }
这里判断了请求方式,若是是HEAD
,GET
,OPTIONS
这三种请求方式则直接放行。你可能会感到疑惑,为何GET请求也要放行呢?这是由于Laravel认为这三个请求都是请求查询数据的,若是一个请求是使用GET方式,那不管请求多少次,不管请求参数如何,都不该该最数据作任何修改。
第二个条件顾名思义是对单元测试进行放行,第三个是为开发者提供了一个能够对某些请求添加例外的功能,最后一个$this->tokensMatch($request)
则是真正起做用的一个,它是Laravel防范CSRF攻击的关键
$sessionToken = $request->session()->token(); $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN'); if (! $token && $header = $request->header('X-XSRF-TOKEN')) { $token = $this->encrypter->decrypt($header); } if (! is_string($sessionToken) || ! is_string($token)) { return false; } return hash_equals($sessionToken, $token);
Laravel会从请求中读取_token
参数的的值,这个值就是在前面表单中添加的CSRF_field()
函数生成的。若是请求是异步的,那么会读取X-CSRF-TOKEN
请求头,从请求头中读取token的值。
最后使用hash_equals
函数验证请求参数中提供的token值和session中存储的token值是否一致,若是一致则说明请求是合法的。
你可能注意到,这个检查过程当中也会读取一个名为X-XSRF-TOKEN
的请求头,这个值是为了提供对一些javascript框架的支持(好比Angular),它们会自动的对异步请求中添加该请求头,而该值是从Cookie中的XSRF-TOKEN
中读取的,所以在每一个请求结束的时候,Laravel会发送给客户端一个名为XSRF-TOKEN
的Cookie值
$response->headers->setCookie( new Cookie( 'XSRF-TOKEN', $request->session()->token(), time() + 60 * $config['lifetime'], $config['path'], $config['domain'], $config['secure'], false ) );
本文只是对CSRF作了一个简单的介绍,主要是侧重于CSRF是什么以及如何应对CSRF攻击。有一个事实是咱们没法回避的:没有绝对安全的系统,你有一千种防护对策,攻击者就有一千零一种攻击方式,但无论如何,咱们都要尽最大的努力去将攻击者拦截在门外。若是但愿深刻了解如何发起一个CSRF攻击,能够参考一下这篇文章 从零开始学CSRF。
做为一名web方向的研发人员,不管你是从事业务逻辑开发仍是作单纯的技术研究,了解一些安全方面的知识都是颇有必要的,多关注一些安全方向的动态,了解常见的攻击方式以及应对策略,必将在你成长为一名大牛的路上为你“推波助澜”。
本文将会持续修正和更新,最新内容请参考个人 GITHUB 上的 程序猿成长计划 项目,欢迎 Star,更多精彩内容请 follow me。