Created 星期日 28 四月 2019
状态:差 心情:工作做不完,心态爆炸

攻击说明

攻击途径:远程网络
攻击复杂度:中
认证:需要后台用户权限
机密性:完全地
完整性:不受影响,
可用性:受影响,命令代码被执行时,web程序前台无法正常使用

问题产生:

PbootCMS是翱云科技开发的全新内核且永久开源免费的PHP企业网站开发建设管理系统,是一套高效、简洁、 强悍的PHP CMS源码,能够满足各类企业网站开发建设的需要。系统采用简单到想哭的模板标签,只要懂HTML就可快速开发企业网站。官方提供了大量网站模板免费下载和使用,将致力于为广大开发者和企业提供最佳的网站开发建设解决方案。

系统后台修改“全局配置-模型管理”业务功能点,其中可以设定显示该栏目内容所应用的模板文件(html),另外系统的“基础内容-站点信息”功能中,可以设定模板文件所在目录,由于程序没有严格的限制引入的模板文件位置,导致系统可引用任意文件作为模板文件显示,之后根据这个模板文件,生成编译文件,最后编译文件直接被程序include,以至于最终的任意代码执行。

0x01.代码位置:

漏洞代码位置:
\apps\admin\controller\content\SiteController.php第37-74行:

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
// 修改系统设置
public function mod()
{
if (! $_POST) {
return;
}

$data = array(
'title' => post('title'),
'subtitle' => post('subtitle'),
'domain' => post('domain'),
'logo' => post('logo'),
'keywords' => post('keywords'),
'description' => post('description'),
'icp' => post('icp'),
'theme' => post('theme') ?: 'default',
'statistical' => post('statistical'),
'copyright' => post('copyright')
);

path_delete(RUN_PATH . '/config'); // 清理缓存的配置文件
if ($this->model->checkSite()) {
if ($this->model->modSite($data)) {
$this->log('修改系统设置成功!');
success('修改成功!', - 1);
} else {
location(- 1);
}
} else {
$data['acode'] = session('acode');
if ($this->model->addSite($data)) {
$this->log('修改系统设置成功!');
success('修改成功!', - 1);
} else {
location(- 1);
}
}
}

代码分析:这里我们具体跟进$theme变量,跟进到$this->model->addSite()方法中。
代码位置:\apps\admin\model\content\SiteModel.php 第30-33行:

1
2
3
4
5
// 增加系统配置信息
public function addSite($data)
{
return parent::table('ay_site')->insert($data);
}

代码分析:数据插入到数据库中。这里没有对插入的数据做合理的过滤检测,特别是theme变量,允许跨文件目录的字符串插入到数据库中。

之后我们分析程序获取整个模板文件,显示文章内容的代码
代码位置:\apps\home\controller\AboutController.php
第28-73行:

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
43
44
45
46
// 单页内容
public function index()
{
if (! ! $scode = get('scode', 'vars')) {
// 读取数据
if (! $data = $this->model->getAbout($scode)) {
header('HTTP/1.1 404 Not Found');
header('status: 404 Not Found');
$file_404 = ROOT_PATH . '/404.html';
if (file_exists($file_404)) {
require $file_404;
exit();
} else {
error('您访问的内容不存在,请核对后重试!');
}
} else {
// 如果访问的内容和当前区域不一致,则自动切换
if ($data->acode != cookie('lg')) {
//echo 'acode:'.$data->acode.'</br>';
$lgs = $this->config('lgs');
if (isset($lgs[$data->acode])) {
cookie('lg', $data->acode);
}
}
}

// 读取模板
if (! ! $sort = $this->model->getSort($data->scode)) {
if ($sort->contenttpl) {
$content = parent::parser($sort->contenttpl); // 框架标签解析
$content = $this->parser->parserBefore($content); // CMS公共标签前置解析
$content = $this->parser->parserPositionLabel($content, $sort->scode); // CMS当前位置标签解析
$content = $this->parser->parserSortLabel($content, $sort); // CMS分类信息标签解析
$content = $this->parser->parserCurrentContentLabel($content, $sort, $data); // CMS内容标签解析
$content = $this->parser->parserAfter($content); // CMS公共标签后置解析
} else {
error('请到后台设置分类栏目内容页模板!');
}
} else {
error('您访问内容的分类已经不存在,请核对后再试!');
}
} else {
error('您访问的地址有误,必须传递栏目scode参数!');
}
$this->cache($content, true);
}

代码分析:
根据url中的id参数去获取文章内容,数据中包含了程序用来显示数据的模板scode参数,之后根据这个scode参数区查询具体的模板文件名,这里我们详细跟进到$content = parent::parser($sort->contenttpl);该段代码大致是模板文件的编译过程。
代码位置:\core\view\View.php 第87-124行:

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
// 解析模板文件
public function parser($file)
{
// 设置主题
$theme = isset($this->vars['theme']) ? $this->vars['theme'] : 'default';

if (! is_dir($this->tplPath .= '/' . $theme)) { // 检查主题是否存在
if ($theme == 'default') { // 默认主题不存在且未默认的,自动初始化
check_file($this->tplPath . '/index.html', true, '<h2>(- -)欢迎您使用本系统,请开始您的开发旅程吧!</h2>');
} else {
error('模板主题目录不存在!主题路径:' . $this->tplPath);
}
}

// 定义当前应用主题目录
define('APP_THEME_DIR', str_replace(DOC_PATH, '', APP_VIEW_PATH) . '/' . $theme);

$file = str_replace('../', '', $file); // 过滤掉相对路径
$tpl_file = $this->tplPath . '/' . $file; // 模板文件
file_exists($tpl_file) ?: error('模板文件' . $file . '不存在!');
$tpl_c_file = $this->tplcPath . '/' . md5($tpl_file) . '.php'; // 编译文件

$content = file_get_contents($tpl_file) ?: error('模板文件' . $file . '读取错误!'); // 读取模板内容
$content = $this->parserInc($content); // 解析包含文件

// 当编译文件不存在,或者模板文件修改过,则重新生成编译文件
if (! file_exists($tpl_c_file) || filemtime($tpl_c_file) < filemtime($tpl_file) || ! Config::get('tpl_parser_cache')) {
$content = Parser::compile($this->tplPath, $content); // 解析模板
file_put_contents($tpl_c_file, $content) ?: error('编译文件' . $tpl_c_file . '生成出错!请检查目录是否有可写权限!'); // 写入编译文件
}

// 获取编译后内容返回
ob_start();
include $tpl_c_file;
$content = ob_get_contents();
ob_end_clean();
return $content;
}

代码分析:
模板文件名$file拼接文件主题目录的路径(该路径由前面的代码分析,知道其存在任意路径指定的问题),判断其是否存在,获取这个模板文件的内容,生成编译文件,之后直接include包含这个编译文件,最后返回编译后的内容。

0x02.缺陷利用

1.上传恶意代码文件到服务器上:
EMDVC8.png
2.访问“模型管理-模型新增”,添加一个新的模型,模板文件名为“1556361116174310.txt”(上传成功的文件名)
EMDlEq.png

3.访问“基础内容-内容栏目-栏目新增”,添加一条新栏目,设置模板文件为我们之前设定的内容,具体如下所示。这里提交成功后,得到我们新建的这个栏目id=14:
EMDtv4.png
4.最后我们访问“基础内容-站点信息”,修改站点模板(即站点主题文件的路径)为default/../../static/upload/file/20190427/,也就是我们上传成功的文件的目录路径
EMDdbR.png
5.修改成功后,查看我们设置的栏目内容。如下可以看到成功执行了代码:
http://127.0.0.1/index.php/about/14
EMDD56.png

修复建议

1.修改“站点信息”时,不允许设置跨目录的文件路径
2.在“模板管理”设置文件模板时,程序应该判断设置的模板文件是否存在且合法


注:该漏洞博主已提交到CNVD.