平时常常听到人们说别乱点连接,当心有病毒。还有长辈们转发的“天呐~XXX的阴谋,全是病毒”、“XXX惊天大病毒,点了苹果手机就要爆炸!”、“如今转发热门链接会乱扣费!千万别点!”。php
到底长辈们说的这些是对的仍是错的,是真的仍是假的?下面我用通俗易懂的语言为你们剖析。html
首先咱们说说CSRF(Cross-site request forgery),它的中文名称是跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。程序员
简单地说,CSRF就是利用了咱们的登陆状态或者受权状态(请注意“利用”,并无窃取到),而后作一些损害咱们自身利益的事情。ajax
举个例子,CSRF使用咱们微信头像和昵称,而后去跟咱爸妈要钱。“爸,生活费不够了。你打到XXX帐户上”,咱爸看见头像和昵称觉得是亲生的,他就转帐。如此,行骗成功。浏览器
长辈们说得对,连接确实不能随便点。我用简单的代码举个例子。安全
假设咱们有一个银行帐户,其中有一个登陆页面login.php
和付款页面paybill.php
,这些页面都属于咱们信任的网站test.com
(test.com
网站是虚拟的)。服务器
在login.php
中设置cookie进行登陆:微信
<?php
setcookie('uid', 1, time()+86400); echo "your uid is {$_COOKIE['uid']}";
在paybill.php
经过身份验证后进行扣款,可是必须输入收款人和扣款金额:cookie
<?php //身份验证 if (!isset($_COOKIE['uid']) || $_COOKIE['uid']< 0) { die('login error!'); } //金额获取 if (!isset($_GET['money'])) { die('no money'); } //收款人获取 if (!isset($_GET['to_who'])) { die('nobody'); } $uid = $_COOKIE['uid']; $money = $_GET['money']; $to_who = $_GET['to_who']; //此处应该还有相关DB操做,省去一万字 echo "transfer {$money} yuan to {$to_who}!";
刷新一下login.php
,进行登陆(实际的用户登陆更为复杂,这里进行简单模拟了)session
在浏览器访问paybill.php页面,转钱1000元给妈妈。
http://test.com/csrf/paybill.php?money=1000&to_who=mama
这时候黑客发现test.com网站没有作任何防护措施,他马上在本身的网站B上伪造了一个页面,页面上有这么一个连接,他的收款人to_who变成了hacker。
<html> <head> <meta charset="utf-8"> </head> <body> <a href="http://test.com/csrf/paybill.php?money=1000&to_who=hacker" taget="_blank">震惊!!史上尺度最大的照片!!<a/> </body> </html>
假设由于好奇心点击了该链接:
这样就转了1000元给了hacker
为何会出现这种状况,咱们在别的网站点击连接竟然能扣本身帐户的钱?点击连接前,咱们已经登陆了信任网站test.com,而这个test.com/csrf/paybill.php?money=1000&to_who=hacker
这个链接是咱们本身发送的,test.com
会识别当前已经登陆,而后转帐,test.com
网站没法判断究竟是谁让咱们点击的。
从上面这个实例可知,完成CSRF攻击流程:
一、用户登陆了信任的网站A,而且保存登陆状态
二、黑客找出网站A没有防护的连接,经过社会工程学假装,诱导点击。
三、只要登陆状态保持,用户主动访问目标连接,则攻击成功。
有人说那每次访问其余网站,把以前的网站都注销。是的,这个办法能够,但这么作这现实吗?咱们须要注销许多经常使用的网站,下次登陆又要输入用户名和密码,极其反人类。这确定不是最佳办法,防护措施应该让程序员考虑,用户别乱点连接是最重要的。
CSRF的攻击渠道不必定来自其余网站,也能够是广告邮件、QQ空间、微信、facebook等社交媒体或软件。试想一下,若是你的女友知道这个连接,她在QQ上发给你:
http://test.com/csrf/paybill.php?money=1000&to_who=girlfriend
你点击后,那就转了1000元给女友,假设她将money改为10w,后果然的不敢想象,你竟然存了这么私房钱,跪搓衣板吧,钱也都到了你女友帐户上。
好了,小白用户看见这里能够关闭,别乱点连接就对,该给女友的钱的仍是一分不能少。
上面的CSRF能够说至关危险,更新资源的操做不该该使用GET方式,GET方式只应该用于读操做。更新操做必定要使用POST方式,特别涉及到钱的问题。
然而POST方式能够解决大部分的CSRF问题,还有剩下少部分的聪明的黑客,同样可以模拟POST请求,伪造身份进行攻击。
假设paybill.php
咱们修改成POST取:
<?php if (!isset($_COOKIE['uid'])) { die('login error!'); } if (!isset($_POST['money'])) { die('no money'); } if (!isset($_POST['to_who'])) { die('nobody'); } $uid = $_COOKIE['uid']; $money = $_POST['money']; $to_who = $_POST['to_who']; if ($uid > 0) { echo "transfer {$money} yuan to {$to_who}!"; }
访问http://test.com/csrf/paybill.php?money=1000&to_who=girlfriend
,提示没有money
可是道高一尺魔高一丈,聪明的黑客也改进了代码,使用POST提交,而且money改成2000:
<html> <head> <meta charset="utf-8"> </head> <body> <form action="http://test.com/csrf/paybill.php" method="post"> <input type="hidden" name="money" value="2000"> <input type="hidden" name="to_who" value="girlfriend"> <input type="submit" value="点击中大奖"> </form> </body> </html>
点击中大奖,以下(又转了2000元给女友):
抓包结果以下,能够看到Cookie: uid=1
和money=2000&to_who=girlfriend
都已发送过去。
POST http://test.com/csrf/paybill.php HTTP/1.1 Host: test.com Referer: http://hack.com/hack/welcome.php Cookie: uid=1 money=2000&to_who=girlfriend
如此一来,无论哪一种访问方式均可能受到攻击。因此,这并非GET和POST谁更安全的问题,POST只是提升了攻击门槛和成本(其实也就多几行html和js)。
划重点,那么CSRF可以攻击的根本缘由是:服务器没法识别你的来源是否可靠。
那么防护的方法有不少:
一、好比加上验证码。但这么作很繁琐,而且影响用户体验。
二、好比转帐须要二次密码验证,如今不少银行就这么搞的。
三、确认来源是否可靠(推荐)
无论防护方法1仍是2,都是让用户自身再次确认受权。
这种安全防范的事儿,更应该由程序验证。
根据验证是否可靠性思路,能够有如下几种方法:
HTTP协议里面定义了一个访问来源的字段,这个字段叫Referer。黑客伪造的连接或表单是在其余网站上,因此咱们能够判断Referer是否为自身网站,若是是,则容许访问,若是不是,则拒绝访问。
从咱们的网站访问paybill.php,抓包发现Referer是不存在的
"HTTP_REFERER"=>""
从黑客的网站访问paybill.php
,抓包发现Referer来自黑客网站
["HTTP_REFERER"]=> string(35) "http://hack.com/welcome.php" 而后代码里判断: if (HTTP_REFERER!="") { die('多是CSRF攻击,拒绝访问'); } else { die('容许访问'); }
因此咱们只须要拦截Referer就能够判断是否为攻击。
可是这种方法是有缺陷的,上面实验尝试过,若是对方在QQ上发送给你一个连接呢?点击的时候属于主动点击,此时同样没有Referer。程序会把它归属为安全请求,那么就被绕过了。而且若是某些低版本的浏览器存在漏洞(好比IE6),Referer颇有可能被篡改,因此这个方法并不是十全十美。
csrf攻击的核心原理就是利用用户验证信息储存cookie中,发送请求,使得服务器没法判断真伪,而token之因此可以拦截,就是由于它是csrf攻击过程当中几乎不可能伪造的东西。
实现原理:在服务端生成一个随机的token,加入到HTTP请求参数中,服务器拦截请求,查看发送的token和服务端的是否一致,若一致,则容许请求;若不一致,则拒绝请求。
新增form.php
表单页面,将token存入session(不要存在cookie中,你懂的):
<?php session_start(); $csrf_token = md5(openssl_random_pseudo_bytes(32));//生成随机token $_SESSION['token']= $csrf_token; ?> <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>csrf</title> </head> <body> <h1>转帐</h1> <form action="paybill.php" method="POST"> money:<input type="text" name="money"> to_who:<input type="text" name="to_who"> <input type="hidden" name="token" value="<?php echo $csrf_token ?>"> <input type="submit" value="ok"> </form> </body> </html>
在paybill.php
获取token,与session中存储的token判断是否一致:
<?php session_start(); if (! isset($_COOKIE['uid'])) { die('login error!'); } if (! isset($_POST['money'])) { die('no money'); } if (! isset($_POST['to_who'])) { die('nobody'); } if (! isset($_POST['token']) || $_POST['token'] != $_SESSION['token']) { die('forbidden'); } $uid = $_COOKIE['uid']; $money = $_POST['money']; $to_who = $_POST['to_who']; if ($uid > 0) { echo "transfer {$money} yuan to {$to_who}!"; }
查看页面form.php
:
请求成功:
每次访问表单页面,都应该生成一个token:
<input type="hidden" name="token" value="a88f67a7effa917450cff12e179df35d">
咱们再尝试从黑客网站进行访问,显示”forbidden”,证实在token验证时被拦截:
这样子就已经有效防护csrf攻击。该方法能够用于a连接和表单等请求,属于同一个原理。
注意:网上不少文章并无生成惟一的或者随机性较大的token,都是同一个token,这是有问题的,若是黑客看到该token,同样能够伪造请求,进行攻击。
实际上A jax防护的思想也能够利用上面的token验证方式。
在IBM上看过一篇文章说Ajax防护时,在 HTTP 头中自定义属性并验证token。
它是这么说的:
把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。经过 XMLHttpRequest 这个类,能够一次性给全部该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,经过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担忧 token 会透过 Referer 泄露到其余网站中去。
我的以为不须要如此麻烦,易用性也不太好,直接对Aajx进行一次封装,加入一个open_token的选项,true就把token也发送过去,不然不进行验证,原理和上面是同样的。
最好将token赋值给js的一个全局变量,整个网站均可以使用。
CSRF防护原则:
GET方式不能用于更新资源的操做
POST方式请求加上随机token验证
OWASP 2017年的十大安全威胁已经公布了,咱们能够看看2013年和2017年CSRF稳稳排在第八位。
总之,CSRF是一种常见的Web安全威胁,它攻击特色是利用用户身份信息假装,发送请求,形成危害。这种攻击成本极低,但网站和用户不注意,很容易受到伤害。固然,更使人欣赏的是黑客利用社会工程学欺骗大众,这才是最重要的。
若是你们对社会工程学感兴趣,推荐一部电影——《我是谁:没有绝对安全的系统》,很是精彩。
互联网安全你攻我防,你枪我盾,没有永远灵验的方法,只有学会攻击,才能抵御攻击。
此文已由做者受权腾讯云技术社区发布,转载请注明文章出处