PHP开发中csrf攻击的简单演示和防范


Posted in PHP onMay 07, 2017

csrf攻击,即cross site request forgery跨站(域名)请求伪造,这里的forgery就是伪造的意思。网上有很多关于csrf的介绍,比如一位前辈的文章CSRF的攻击方式详解,参考这篇文章简单解释下:csrf 攻击能够实现依赖于这样一个简单的事实:我们在用浏览器浏览网页时通常会打开好几个浏览器标签(或窗口),假如我们登录了一个站点A,站点A如果是通过cookie来跟踪用户的会话,那么在用户登录了站点A之后,站点A就会在用户的客户端设置cookie,假如站点A有一个页面siteA-page.php(url资源)被站点B知道了url地址,而这个页面的地址以某种方式被嵌入到了B站点的一个页面siteB-page.php中,如果这时用户在保持A站点会话的同时打开了B站点的siteB-page.php,那么只要siteB-page.php页面可以触发这个url地址(请求A站点的url资源)就实现了csrf攻击。

上面的解释很拗口,下面举个简单的例子来演示下。

1,背景和正常的请求流程

A站点域名为html5.yang.com,它有一个/get-update.php?uid=uid&username=username地址,可以看到这个地址可以通过get方法来传递一些参数,假如这个页面的逻辑是:它通过判断uid是否合法来更新username,这个页面脚本如下:

<?php
// 这里简便起见, 从data.json中取出数据代替请求数据库
$str = file_get_contents('data.json');
$data = json_decode($str, true);

// 检查cookie和请求更改的uid, 实际应检查数据库中的用户是否存在
empty($_COOKIE['uid']) ||empty($_GET['uid']) || $_GET['uid'] != $data['id'] ? die('非法用户') : '';
// 检查username参数
$data['username'] = empty($_GET['username']) ? die('用户名不能为空') : $_GET['username'];

// 更新数据
$data['username'] = $_GET['username'];
if(file_put_contents('data.json', json_encode($data))) {
  echo "用户名已更改为{$data['username']}<br>";
} else {
  die('更新失败');
}

正常情况下这个页面的链接是放在站点A下面的,比如A站点的csrfdemo.php页面,用户登录站点A以后可以通过点击这个链接来发送请求,比如站点A有一个页面脚本,包含了这个链接:

<?php
// 这里用一个data.json文件保存用户数据,模拟数据库中的数据
// 先初始化data.json中的数据为{"id":101,"username":"jack"}, 注意这句只让它执行一次, 然后把它注释掉
// file_put_contents('data.json','{"id":101,"username":"jack"}');

$data = json_decode(file_get_contents('data.json'), true);

// 这里为了简便, 省略了用户身份验证的过程
if ($data['username']) {
  // 设置cookie
  setcookie('uid', $data['id'], 0);
  echo "登录成功, {$data['username']}<br>";
}
?>

 <a href="http://html5.yang.com/csrfdemo/get-update.php?uid=101&username=json" rel="external nofollow" >
  更新用户名为json
 </a>

加载这个页面如下:

PHP开发中csrf攻击的简单演示和防范

用点击页面中的链接来到get-update.php页面:

PHP开发中csrf攻击的简单演示和防范

上面是正常的请求流程,下面来看B站点是如何实现csrf攻击的。

2,csrf攻击的最简单实现

B站点域名为test.yang.com,它有一个页面csrf.php,只要用户在维持A站点会话的同时打开了这个页面,那么B站点就可以实现csrf攻击。至于为什么会打开......,其实这种情景在我们浏览网页时是很常见的,比如我在写这篇博客时,写着写着感觉对csrf某个地方不懂,然后就百度了,结果百度出来好多结果,假如说有个网站叫csrf百科知识,这个网站对csrf介绍的非常详细、非常权威,那么我很可能会点进去看,但是这个网站其实是个钓鱼网站,它在某个访问频率很高的页面中嵌入了我博客编辑页面的url地址,那么它就可以实现对我博客的csrf攻击。好了,言归正传,下面来看下csrf.php脚本代码:

<?php
?>
<img src="http://html5.yang.com/csrfdemo/get-update.php?uid=101&username=jsonp">
可以看到上面的代码没有php代码,只有一个img标签,img标签的src就是A站点的那个更新用户名的链接,只不过把username改为了jsonp,访问站点B的csrf.php这个页面:

PHP开发中csrf攻击的简单演示和防范

下面再来访问下A站点的csrfdemo.php页面:

PHP开发中csrf攻击的简单演示和防范

可以看到用户名被修改为了jsonp。

简单分析下:B站点的这个csrf.php利用了html中的img标签,我们都知道img标签有个src属性,属性值指向需要加载的图片地址,当页面载入时,加载图片就相当于向src指向的地址发起http请求,只要把图片的地址修改为某个脚本地址,这样自然就实现了最简单的csrf攻击。如此说来,其实csrf很容易实现,只不过大家都是“正人君子”,谁没事会闲着去做这种“下三滥”的事情。但是害人之心不可有,防人之心不可无。下面看下如何简单防范这种最简单的csrf攻击。

3,简单防范措施

其实防范措施也比较简单,A站点可以在get-update.php脚本中判断请求头的来源,如果来源不是A站点就可以截断请求,下面在get-update.php增加些代码: 

<?php
// 检查上一页面是否为当前站点下的页面
if (!empty($_SERVER['HTTP_REFERER'])) {
  if (parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) != 'html5.yang.com') {
    // 可以设置http错误码或者指向一个无害的url地址
    //header('HTTP/1.1 404 not found');
    //header('HTTP/1.1 403 forbiden');
    header('Location: http://html5.yang.com/favicon.ico');
    // 这里需要注意一定要exit(), 否则脚本会接着执行
    exit();
  }
 }

$str = file_get_contents('data.json');
// 代码省略

但是,这样就万事大吉了吗,如果http请求头被伪造了呢?A站点升级了防御,B站点同时也可以升级攻击,通过curl请求来实现csrf,修改B站点的csrf.php代码如下:

<?php
$url = 'http://html5.yang.com/csrfdemo/get-update.php?uid=101&username=jsonp';
$refer = 'http://html5.yang.com/';
// curl方法发起csrf攻击
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
// 设置Referer
curl_setopt($ch, CURLOPT_REFERER, $refer);
// 这里需要携带上cookie, 因为A站点get-update.php对cooke进行了判断
curl_setopt($ch, CURLOPT_COOKIE, 'uid=101');
curl_exec($ch);
curl_close($ch);
?>
<img src="http://html5.yang.com/csrfdemo/get-update.php?uid=101&username=jsonp">
这样同样可以实现csrf攻击的目的。那么就没有比较好的防范方法了吗?

4,小结

下面我们回到问题的开始,站点A通过cookie来跟踪用户会话,在cookie中存放了重要的用户信息uid,get-update.php脚本通过判断用户的cookie正确与否来决定是否更改用户信息,看来靠cookie来跟踪会话并控制业务逻辑是不太安全的,还有最严重的一点:get-update.php通过get请求来修改用户信息,这个是大忌。所以站点A可以接着升级防御:用session来代替cookie来跟踪用户会话信息,将修改用户信息的逻辑重写,只允许用post方法来请求用户信息。站点B同样可以升级攻击:curl可以构造post请求,劫持session等等,不过这些我还没研究过,后续再说吧。

PHP 相关文章推荐
提升PHP执行速度全攻略(下)
Oct 09 PHP
MySQL相关说明
Jan 15 PHP
php下用GD生成生成缩略图的两个选择和区别
Apr 17 PHP
php 的加密函数 md5,crypt,base64_encode 等使用介绍
Apr 09 PHP
深入探讨:Nginx 502 Bad Gateway错误的解决方法
Jun 03 PHP
Php中使用Select 查询语句的实例
Feb 19 PHP
PHP实现对站点内容外部链接的过滤方法
Sep 10 PHP
CodeIgniter针对lighttpd服务器URL重写的方法
Jun 10 PHP
分享PHP函数实现数字与文字分页代码
Jul 28 PHP
ThinkPHP框架整合微信支付之Native 扫码支付模式二图文详解
Apr 09 PHP
PHP生成随机码的思路与方法实例探索
Apr 11 PHP
php+iframe 实现上传文件功能示例
Mar 04 PHP
ThinkPHP框架实现数据增删改
May 07 #PHP
thinkphp 验证码 的使用小结
May 07 #PHP
解析 thinkphp 框架中的部分方法
May 07 #PHP
ThinkPHP 模板引擎使用详解
May 07 #PHP
php中Ioc(控制反转)和Di(依赖注入)
May 07 #PHP
Laravel中任务调度console使用方法小结
May 07 #PHP
Laravel实现表单提交
May 07 #PHP
You might like
基于mysql的论坛(6)
2006/10/09 PHP
PHP学习资料汇总与网址
2007/03/16 PHP
PHP中的gzcompress、gzdeflate、gzencode函数详解
2014/07/29 PHP
thinkphp分页实现效果
2016/10/13 PHP
jquery select下拉框操作的一些说明
2010/04/02 Javascript
JavaScript 选中文字并响应获取的实现代码
2011/08/28 Javascript
IE下写xml文件的两种方式(fso/saveAs)
2013/08/05 Javascript
关于JS中的闭包浅谈
2013/08/23 Javascript
JavaScript中的对象与JSON
2015/07/03 Javascript
理解javascript中DOM事件
2015/12/25 Javascript
ThinkJS中如何使用MongoDB的CURD操作
2016/12/13 Javascript
js 性能优化之算法和流程控制
2017/02/15 Javascript
初学者AngularJS的环境搭建过程
2017/10/27 Javascript
vue-cli2.9.3 详细教程
2018/04/23 Javascript
用webpack4开发小程序的实现方法
2019/06/04 Javascript
原生js实现随机点餐效果
2019/12/10 Javascript
JavaScript实现Excel表格效果
2020/02/07 Javascript
javascript History对象原理解析
2020/02/17 Javascript
使用Vue实现一个树组件的示例
2020/11/06 Javascript
Python编程pygal绘图实例之XY线
2017/12/09 Python
Python中存取文件的4种不同操作
2018/07/02 Python
Python如何在DataFrame增加数值
2020/02/14 Python
python openCV自制绘画板
2020/10/27 Python
HTML5 video播放器全屏(fullScreen)方法实例
2015/04/24 HTML / CSS
Levi’s美国官网:美国著名的牛仔裤品牌
2016/08/19 全球购物
俄罗斯电子产品、计算机和家用电器购物网站:OLDI
2019/10/27 全球购物
C#面试常见问题
2013/02/25 面试题
北京某科技有限公司C# .net笔试题
2014/09/27 面试题
大学生就业自我推荐信
2014/05/10 职场文书
公安局班子个人对照检查材料思想汇报
2014/10/09 职场文书
初二学生评语大全
2014/12/26 职场文书
大学生在校表现评语
2014/12/31 职场文书
本科毕业论文致谢怎么写
2015/05/14 职场文书
Oracle数据库中通用的函数实例详解
2022/03/25 Oracle
python lambda 表达式形式分析
2022/04/03 Python
使用Postman测试需要授权的接口问题
2022/06/21 Java/Android