解析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 相关文章推荐
解决MySQL中文输出变成问号的问题
Jun 05 PHP
php下过滤html代码的函数 提高程序安全性
Mar 02 PHP
PHP+SQL 注入攻击的技术实现以及预防办法
Jan 27 PHP
Apache2中实现多网站域名绑定的实现方法
Jun 01 PHP
如何使用php判断服务器是否是HTTPS连接
Jul 05 PHP
PHP生成Gif图片验证码
Oct 27 PHP
PHP整数取余返回负数的相关解决方法
May 15 PHP
thinkPHP实现表单自动验证
Dec 24 PHP
php基于Fleaphp框架实现cvs数据导入MySQL的方法
Feb 23 PHP
php中照片旋转 (orientation) 问题的正确处理
Feb 16 PHP
PHP多个图片压缩成ZIP的方法
Aug 18 PHP
关于Yii中模型场景的一些简单介绍
Sep 22 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命令行(cli)下执行PHP脚本文件的相对路径的问题解决方法
2015/05/25 PHP
详解PHP错误日志的获取方法
2015/07/20 PHP
ThinkPHP开发框架函数详解:C方法
2015/08/14 PHP
PHP框架Laravel学习心得体会
2015/10/28 PHP
PHP使用ActiveMQ实例
2018/02/05 PHP
PHP基于redis计数器类定义与用法示例
2018/02/08 PHP
解析DHTML,JavaScript,DOM,BOM以及WEB标准的描述
2013/06/19 Javascript
javascript实现跳转菜单的具体方法
2013/07/05 Javascript
Jquery 复选框取值兼容FF和IE8(测试有效)
2013/10/29 Javascript
如何获取select下拉框的值(option没有及有value属性)
2013/11/08 Javascript
Vue调试神器vue-devtools安装方法
2017/12/12 Javascript
vue项目首屏打开速度慢的解决方法
2019/03/31 Javascript
微信小程序实现锚点功能
2019/11/20 Javascript
JavaScript实现英语单词题库
2019/12/24 Javascript
基于vue--key值的特殊用处详解
2020/07/31 Javascript
[53:13]2014 DOTA2国际邀请赛中国区预选赛5.21 DT VS LGD-GAMING
2014/05/22 DOTA
[03:33]TI9战队采访 - Infamous
2019/08/20 DOTA
Python实现快速多线程ping的方法
2015/07/15 Python
Python 列表(List) 的三种遍历方法实例 详解
2017/04/15 Python
Django的HttpRequest和HttpResponse对象详解
2018/01/26 Python
详解python的ORM中Pony用法
2018/02/09 Python
python实现12306抢票及自动邮件发送提醒付款功能
2018/03/08 Python
python中字符串比较使用is、==和cmp()总结
2018/03/18 Python
python3实现随机数
2018/06/25 Python
解决使用pycharm提交代码时冲突之后文件丢失找回的方法
2018/08/05 Python
基于pycharm导入模块显示不存在的解决方法
2018/10/13 Python
基于django channel实现websocket的聊天室的方法示例
2019/04/11 Python
在flask中使用python-dotenv+flask-cli自定义命令(推荐)
2020/01/05 Python
Python调用REST API接口的几种方式汇总
2020/10/19 Python
Python爬虫开发与项目实战
2020/12/16 Python
python 使用openpyxl读取excel数据
2021/02/18 Python
Python Pygame实现俄罗斯方块
2021/02/19 Python
时尚休闲吧创业计划书
2014/01/25 职场文书
死亡诗社观后感
2015/06/05 职场文书
史上最全书信经典范文大全(建议收藏)
2019/07/10 职场文书
JavaScript ES6的函数拓展
2022/01/18 Javascript