解析PHP强制转换类型及远程管理插件的安全隐患


Posted in PHP onJune 30, 2014

远程管理插件是目前广受WordPress站点管理员欢迎的实用工具,它允许用户同时对多个站点执行相同的操作,如更新到最新的发行版或安装插件等。但是,为了实现这些操作,客户端插件需要赋予远程用户很大的权限。因此,确保管理服务器和客户端插件之间的通信安全且不能被攻击者伪造就变得相当重要了。本文浅析几款可用插件,利用其弱点,攻击者甚至可以完全危及到运行这些插件的站点本身。

ManageWP, InfiniteWP, and CMS Commander

这三个服务有着相同的客户端插件基础代码(目测最初是ManageWp实现,然后另外两个对其进行了调整),因而它们都有签名绕过漏洞并且会导致远程代码执行。

管理服务器注册一个客户端插件的私钥,用来计算每一条消息的消息认证码,而不是要求用户提供管理员凭证(MAC,我们平时看到它会将其当做硬件的MAC地址,这里是Message Authentication Code)。当一条消息通过使用共享密钥的消息摘要算法后就生成了消息摘要。该MAC随后附在消息后面一起发送出去,接收方收到后用共享秘钥对收到的消息进行计算,生成MAC2,然后和MAC1进行比较。消息摘要用于验证消息的真实性和完整性(学过密码学的同学对此应该都知道),这是一个确保通信安全的好方法,但是这三项服务的客户端插件在实现上的缺陷就导致了严重的漏洞。

一条由helper.class.php认证的传入消息如下所示:

// $signature is the MAC sent with the message 
// $data is part of the message 
if (md5($data . $this->get_random_signature()) == $signature) { 
// valid message 
}

使用非严格的等于意味着在比较前会发生类型“欺骗”[类型转换]。md5()函数的输出永远都是字符串,但是如果$signature变了是一个整数,那么比较时发生的类型转换就容易伪造一个匹配的MAC。例如,如果真实的MAC是以”0”开头,或者非数字字符开头,那么0就能匹配上,如果是”1xxx”这样的,那么整数1就能匹配,以此类推。(这里其实是php的一个特性,当然其他语言也会有,当一个字符串和数字进行非严格等于的比较时,如果第一个字符是数字就会将其转换成对应的整数进行比较,如果是非0-9的字符,就会将其当做0,php.net官方的说明:如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换为数值并且比较按照数值来进行)。

字符串转换为数值:

当一个字符串被当作一个数值来取值,其结果和类型如下:

如果该字符串没有包含 '.','e' 或 'E' 并且其数字值在整型的范围之内(由 PHP_INT_MAX 所定义),该字符串将被当成integer 来取值。其它所有情况下都被作为 float 来取值。

该字符串的开始部分决定了它的值。如果该字符串以合法的数值开始,则使用该数值。否则其值为 0(零)。合法数值由可选的正负号,后面跟着一个或多个数字(可能有小数点),再跟着可选的指数部分。指数部分由 'e' 或 'E' 后面跟着一个或多个数字构成。

<?php
var_dump(0 == "a"); // 0 == 0 -> true
var_dump("1" == "01"); // 1 == 1 -> true
var_dump("10" == "1e1"); // 10 == 10 -> true
var_dump(100 == "1e2"); // 100 == 100 -> true
var_dump('abcdefg' == 0); // true 
var_dump('1abcdef' == 1); // true 
var_dump('2abcdef' == 2); // true 
?>

遗憾的是,攻击者可以提供一个整数作为签名。init.php中,传入的请求将会使用base64_decode()解码,然后反序列化其结果。Unserialize()的使用意味着可以控制输入数据的类型,一个伪造的序列化消息如下:

a:4:{s:9:"signature";i:0;s:2:"id";i:100000;s:6:"action";s:16:"execute_php_code";s:6:"params";a:2:{s:8:"username";s:5:"admin";s:4:"code";s:25:"exec('touch /tmp/owned');";}}
这条消息使用整数0作为签名,然后使用插件提供的execute_php_code执行任意的PHP代码。

$signature = 0; 
// $data is the action concatenated with the message ID 
$data = 'execute_php_code' . 100000; 
if (md5($data . $this->get_random_signature()) == $signature) { 
  // valid message if the output of 
  // md5() doesn't start with a digit 
}

这个伪造的例子可能没法直接使用,首先,id的键值需要比之前合法消息的值更大(使用增加的消息ID用于防止重放攻击,今天既有请求伪造,又有重放,这让我想到了CSRF,跨站请求伪造,下面是不是还有中间人攻击呢),其次要有用于匹配签名的整数,这两点要求可以进行暴力破解来突破。

for i from 100,000 to 100,500:
  for j from 0 to 9:
    submit request with id i and signature j

上面的伪代码尝试发送具有很大ID值得虚假消息,并且对每个ID都进行十次单独的数字指纹匹配(前面说到过,对于一个字符串,只要一个数字就可以在比较时进行匹配,这里从0-9是因为每一种情况都能遇到)。

这一缺陷可以通过使用全等运算符[===]和对传入的指纹进行检查来修复。这几个插件服务都通过使用严格的全等运算符进行了修复(php.net的说明:a===b,则a和b值相等,且类型也相等;a==b,在发生类型转换后再判断其值是否相等)。

另外还有一些其他的问题,但是他们还没有采取行动。首先,这一做法是有弱点的(密钥追加到$data,然后进行散列),应该用HMAC(Hash-based Message Authentication Code,以一个密钥和一个消息为输入,生成一个消息摘要作为输出)。其次,仅用于操作的action和消息ID被用于创建签名。这意味着,一个活跃的网络攻击者可以改变消息中的参数而签名依旧是有效的(例如改变execute_php_code消息执行任意代码)。为了进行保护,MAC应该包含整条消息。

(注意,基于MD5的消息摘要是一种后退,可以的话这些插件使用openssl_verify();***2014-04公布出来的Openssl 1.0.f heartbleed漏洞号称世纪级漏洞***)

WORPIT

Worpit是另一个远程管理服务,但它使用从头开始构建的客户端插件,它同样有强制类型转换漏洞,可以让攻击者以管理员权限登陆。

该插件提了远程管理员登陆的方法,使用仅Woprit传递系统可配置的临时的token值。这款插件会检查请求中提供的token值是否和存储在数据库中的值匹配。

if ( $_GET['token'] != $oWpHelper->getTransient( 'worpit_login_token' ) ) { 
  die( 'WorpitError: Invalid token' ); 
}

令牌是从一次使用的数据库中删除。这意味着大多数的时候都是在数据库中没有令牌。因此,在调用getTransient()方法可能返回false。非严格的比较是,这意味着任何“falsey价值,比如字符串0,将被视为一个有效的令牌。一个例子网址以管理员身份登录:

这个token一经使用就会从数据库中删除,这意味着,大多数时候数据库中是没有token的。因此,对getTransient()方法的调用很可能返回false。非严格的比较也用到了,这意味着任何相当于false的值,例如字符串0会被当做一个有效的token,以管理员身份登陆的例子如:http://victim/?worpit_api=1&m=login&token=0

至此,该站点就为攻击者所控制了,他有权限安装恶意插件或修改已有的插件。

这里的修复方案是使用!==并进行其他检查及从数据库进行检索。

结论:

一定要记住检查用户输入的是预期的类型并在安全性很重要的函数中使用进行严格比较,如检查身份验证令牌。

PHP 相关文章推荐
PHP实现多服务器session共享之NFS共享的方法
Mar 16 PHP
PHP面向对象——访问修饰符介绍
Nov 08 PHP
php中get_headers函数的作用及用法的详细介绍
Apr 27 PHP
php中判断数组是一维,二维,还是多维的解决方法
May 04 PHP
关于url地址传参数时字符串有回车造成页面脚本赋值失败的解决方法
Jun 28 PHP
使用php记录用户通过搜索引擎进网站的关键词
Feb 13 PHP
理解PHP中的stdClass类
Apr 18 PHP
php+mysqli使用面向对象方式更新数据库实例
Jan 29 PHP
详解php设置session(过期、失效、有效期)
Nov 12 PHP
php文件操作小结(删除指定文件/获取文件夹下的文件名/读取文件夹下图片名)
May 09 PHP
php-msf源码详解
Dec 25 PHP
Nginx+php配置文件及原理解析
Dec 09 PHP
PHP数字和字符串ID互转函数(类似优酷ID)
Jun 30 #PHP
PHP把数字转成人民币大写的函数分享
Jun 30 #PHP
PHP统计nginx访问日志中的搜索引擎抓取404链接页面路径
Jun 30 #PHP
PHP把JPEG图片转换成Progressive JPEG的方法
Jun 30 #PHP
PHP把小数转成整数3种方法
Jun 30 #PHP
php 无限级分类,超级简单的无限级分类,支持输出树状图
Jun 29 #PHP
php防止伪造的数据从URL提交方法
Jun 27 #PHP
You might like
学习php中的正则表达式
2014/08/17 PHP
php准确计算复活节日期的方法
2015/04/18 PHP
PHP addAttribute()函数讲解
2019/02/03 PHP
JavaScript入门教程(7) History历史对象
2009/01/31 Javascript
测试你的JS的掌握程度的代码
2009/12/09 Javascript
javascript定时变换图片实例代码
2013/03/17 Javascript
javaScript面向对象继承方法经典实现
2013/08/20 Javascript
在Javascript中处理字符串之big()方法的使用
2015/06/08 Javascript
基于javascript实现动态显示当前系统时间
2016/01/28 Javascript
vue2.0开发实践总结之疑难篇
2016/12/07 Javascript
webpack入门必知必会
2017/01/16 Javascript
AngularJs上传前预览图片的实例代码
2017/01/20 Javascript
jQuery中animate()的使用方法及解决$(”body“).animate({“scrollTop”:top})不被Firefox支持的问题
2017/04/04 jQuery
js+html制作简单日历的方法
2017/06/27 Javascript
JS实现带动画的回到顶部效果
2017/12/28 Javascript
vue计算属性时v-for处理数组时遇到的一个bug问题
2018/01/21 Javascript
js闭包学习心得总结
2018/04/17 Javascript
vue响应式更新机制及不使用框架实现简单的数据双向绑定问题
2019/06/27 Javascript
vue将后台数据时间戳转换成日期格式
2019/07/31 Javascript
VUE单页面切换动画代码(全网最好的切换效果)
2019/10/31 Javascript
[01:34]2014DOTA2展望TI 剑指西雅图VG战队专访
2014/06/30 DOTA
[02:16]完美世界DOTA2联赛PWL S3 集锦第三期
2020/12/21 DOTA
Python批量修改文本文件内容的方法
2016/04/29 Python
Python自动化测试Eclipse+Pydev 搭建开发环境
2016/08/15 Python
解决pandas无法在pycharm中使用plot()方法显示图像的问题
2018/05/24 Python
Django的用户模块与权限系统的示例代码
2019/07/24 Python
python操作cfg配置文件方式
2019/12/22 Python
css3中新增的样式使用示例附效果图
2014/08/19 HTML / CSS
美国一家主打母婴用品的团购网站:zulily
2017/09/19 全球购物
家乐福台湾线上购物网:Carrefour台湾
2020/09/15 全球购物
中软国际Java程序员机试题
2012/08/19 面试题
运动会广播稿500字
2014/01/28 职场文书
残疾人创业典型事迹
2014/02/01 职场文书
小学社会实践活动总结
2014/07/03 职场文书
房屋鉴定委托书范本
2014/09/23 职场文书
幼儿园中班教师个人总结
2015/02/05 职场文书