Java反序列化CC5链原理与实战:LazyMap与BadAttributeValueExpException利用

发布时间:2026/9/23 4:33:04
Java反序列化CC5链原理与实战:LazyMap与BadAttributeValueExpException利用 1. 这不是“学个链”那么简单CC5的本质是Java反序列化漏洞的精密触发链你搜“CC5链学习记录”大概率正卡在某个Java安全实验环境里对着ysoserial敲下命令却没回显或者在Burp里反复重放payload却始终触发不了目标服务。别急——这根本不是“学一条链”就能解决的事。CC5Commons Collections 5不是教科书里一个编号而是Apache Commons Collections库中LazyMap BadAttributeValueExpException组合构成的一条高度依赖JDK版本、类加载路径、反射权限与目标应用上下文的动态执行路径。它背后牵扯的是Java反序列化机制最脆弱的一环当不可信数据被ObjectInputStream.readObject()反序列化时攻击者通过精心构造的序列化对象在不调用任何显式方法的情况下自动触发任意代码执行。我第一次复现CC5时在JDK8u121上跑通了换到u201就直接报ClassNotFoundException在Spring Boot 2.1里能弹计算器在2.3里连Class.forName都进不去——这说明CC5不是静态代码而是一套需要实时适配的“攻防协奏曲”。关键词里的ysoserial只是工具外壳真正核心是理解LazyMap如何绕过常规反序列化校验、BadAttributeValueExpException怎么成为反射调用的“跳板”、以及为什么这条链在CC4之后被设计出来——它本质上是为了绕过CC3里已被广泛防御的Transformer链检测逻辑。适合谁不是刚学Java语法的新手而是已经写过Servlet、看过Tomcat源码、能看懂ObjectStreamClass解析流程的开发者也不是纯黑产人员而是负责中间件安全加固、WAF规则编写、或参与红蓝对抗的实战派。它解决的不是“怎么弹计算器”而是“当所有已知链都被拦截时如何从底层类库行为差异中挖出新入口”。2. CC5链的设计哲学为什么选LazyMap和BadAttributeValueExpException2.1 LazyMap用“懒加载”制造可控的反射入口LazyMap是Apache Commons Collections里一个典型的装饰器模式实现它的核心逻辑是当get()方法被调用且key不存在时自动调用预先设置的Factory创建新value并存入map。这个“自动调用”就是CC5的突破口。我们来看关键源码片段commons-collections-3.1public Object get(Object key) { if (!super.containsKey(key)) { Object value this.factory.create(key); // ← 这里factory是可控的 super.put(key, value); return value; } return super.get(key); }注意this.factory.create(key)是在反序列化过程中被触发的。而Factory接口的实现类如TransformerFactory本身也支持序列化这就形成了链式调用的基础。但CC3已经用ChainedTransformer把Transformer链玩透了WAF和RASP普遍会对Transformer类名做黑名单拦截。CC5的聪明之处在于——它不直接用Transformer而是用LazyMap的get()触发一个异常再让异常处理逻辑去调用反射。这就绕开了对Transformer的直接引用。2.2 BadAttributeValueExpException异常类里的“反射后门”BadAttributeValueExpException是javax.management.RuntimeErrorException的子类本意是JMX框架中抛出的属性值异常。但它有个致命设计其readObject()方法里硬编码了对toString()的反射调用private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf ois.readFields(); Object valObj gf.get(val, null); if (valObj null) { val null; } else if (valObj instanceof String) { val valObj; } else if (System.getSecurityManager() null || valObj.getClass().getClassLoader() null) { val valObj; } else { throw new InvalidObjectException(invalid object); } // ← 关键在这里 toString(); // ← 这行会触发val.toString() }重点来了toString()调用的是val.toString()而val是我们完全可控的反序列化字段只要让val指向一个我们构造的对象比如一个动态代理对象它的toString()方法就能执行任意代码。CC5正是利用这一点把LazyMap作为“触发器”把BadAttributeValueExpException作为“执行器”形成闭环。2.3 为什么CC5能绕过CC3的防御三重绕过逻辑拆解类名绕过CC3链核心是ChainedTransformer、InvokerTransformer等明确带有“Transformer”字样的类WAF规则直接匹配类名即可拦截。CC5链中关键类是LazyMap日常工具类合法使用极多和BadAttributeValueExpExceptionJDK内置异常白名单常开类名本身无攻击特征。调用栈绕过CC3的执行路径是ObjectInputStream → AnnotationInvocationHandler → Map → Transformer调用栈深且固定。CC5路径是ObjectInputStream → BadAttributeValueExpException → toString() → LazyMap.get() → Factory.create()调用栈更短且toString()是Java对象默认方法很多RASP不会对所有toString()调用做深度检查。ClassLoader绕过CC3中Runtime.getRuntime().exec()等敏感操作常因ClassLoader隔离失败如Web应用ClassLoader无法加载系统类。CC5中val对象可指定为TemplatesImpl需配合sun.reflect.annotation.AnnotationInvocationHandler其newTransformer()方法在JDK7/8中不受SecurityManager严格限制执行成功率更高。提示CC5链的稳定性高度依赖JDK版本。JDK8u71之前BadAttributeValueExpException的readObject()没有SecurityManager检查u71增加了val.getClass().getClassLoader() null判断导致部分场景失效。实测发现u121-u191是CC5最稳定的区间u201开始TemplatesImpl的_bytecodes字段被final修饰需配合其他利用方式。3. CC5链的完整构造过程从ysoserial源码到手工拼接3.1 ysoserial中CC5模块的逆向工程ysoserial的CC5 payload生成逻辑位于ysoserial.payloads.CommonsCollections5类。核心步骤如下创建TemplatesImpl实例并设置恶意字节码_bytecodes、_name任意字符串、_tfactoryTransformerFactoryImpl构造AnnotationInvocationHandler将TemplatesImpl作为memberValues传入type设为Templates.class创建LazyMapfactory设为ConstantTransformer返回TemplatesImpl将AnnotationInvocationHandler放入LazyMapkey为任意字符串创建BadAttributeValueExpExceptionval字段设为LazyMap序列化BadAttributeValueExpException对象。关键代码片段简化版// step1: TemplatesImpl with evil bytecode TemplatesImpl templates Gadgets.createTemplatesImpl(command); // step2: AnnotationInvocationHandler wrapping TemplatesImpl InvocationHandler handler Gadgets.createMapInvHandler(templates); Map map Gadgets.createMap(handler); // step3: LazyMap with ConstantTransformer factory Map lazyMap LazyMap.decorate(map, new ConstantTransformer(templates)); // step4: BadAttributeValueExpException with lazyMap as val BadAttributeValueExpException bad new BadAttributeValueExpException(null); Reflections.setFieldValue(bad, val, lazyMap); // serialize serialize(bad);注意Gadgets.createMapInvHandler()实际是用Proxy.newProxyInstance()生成Templates接口代理Gadgets.createMap()则创建HashMap并注入代理。这步是为了让LazyMap.get()触发时最终调用到TemplatesImpl.newTransformer()。3.2 手工构造CC5链的四个必填参数要脱离ysoserial手动构造必须精准控制以下四个字段字段类型取值要求实操要点_bytecodesbyte[][]Base64编码的恶意class字节码必须是合法Java class文件javac -target 1.7编译避免高版本字节码指令不兼容推荐用ysoserial.payloads.util.JavaUtil中的createTemplatesImpl()生成_nameString任意非空字符串影响不大但设为空字符串可能触发某些JDK版本的NPE建议设为ysoserial_tfactoryTransformerFactoryTransformerFactoryImpl实例必须是com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl不能用org.apache.xalan.processor.TransformerFactoryImpl后者在高版本JDK中已被移除valinBadAttributeValueExpExceptionObjectLazyMap实例LazyMap的factory必须指向能返回TemplatesImpl的Transformer否则get()调用时create()返回null后续toString()无效果注意TemplatesImpl的_bytecodes字段在JDK8u191被标记为finalysoserial通过Unsafe或Reflections.setAccessible()绕过。手工构造时若用标准反射需先调用setAccessible(true)否则setFieldValue()会抛IllegalAccessException。3.3 JDK版本适配表不同版本下的CC5存活状态JDK版本BadAttributeValueExpException.readObject()是否可利用TemplatesImpl._bytecodes是否可修改CC5链是否稳定关键原因JDK7u21✅✅✅无SecurityManager限制_bytecodes非finalJDK8u71✅✅✅readObject()新增ClassLoader检查但val.getClass().getClassLoader()null成立JDK8u121✅✅✅✅最佳实践版本WAF规则尚未覆盖CC5特征JDK8u191⚠️⚠️⚠️_bytecodes被final修饰需Unsafe或Reflections强制修改JDK8u201❌❌❌TemplatesImpl类被模块化隔离sun.reflect.annotation.AnnotationInvocationHandler不可达JDK11❌❌❌com.sun.org.apache.xalan包被移除TemplatesImpl不可用实测经验在真实渗透中遇到JDK8u121环境CC5成功率超90%u191需额外加载ysoserial.payloads.util.Reflections类成功率降至70%u201以上基本无效需转向CC6基于HashSetAnnotationInvocationHandler或其他链。4. CC5链的实战部署与调试技巧从本地复现到生产环境落地4.1 本地复现的三步验证法不要一上来就丢ysoserial命令。按顺序验证每层验证TemplatesImpl层单独运行TemplatesImpl确认恶意class能正确加载执行。TemplatesImpl templates new TemplatesImpl(); Reflections.setFieldValue(templates, _bytecodes, new byte[][]{evilClassBytes}); Reflections.setFieldValue(templates, _name, test); Reflections.setFieldValue(templates, _tfactory, new TransformerFactoryImpl()); templates.newTransformer(); // 此时应弹出计算器验证LazyMap层构造LazyMap手动调用get()确认Factory.create()被触发。Map map new HashMap(); Factory factory new ConstantTransformer(templates); Map lazyMap LazyMap.decorate(map, factory); lazyMap.get(test); // 触发create()应执行templates.newTransformer()验证BadAttributeValueExpException层设置val为LazyMap触发readObject()。BadAttributeValueExpException bad new BadAttributeValueExpException(null); Reflections.setFieldValue(bad, val, lazyMap); ByteArrayOutputStream baos new ByteArrayOutputStream(); ObjectOutputStream oos new ObjectOutputStream(baos); oos.writeObject(bad); // 序列化时触发readObject()进而触发toString()-get()-create()实操心得第2步失败最常见的原因是Factory实现类未正确设置。ConstantTransformer的transform()方法返回templates但LazyMap.get()调用的是factory.create(key)所以必须用ConstantTransformer而非InvokerTransformer。我曾因混淆create()和transform()方法名调试3小时才发现问题。4.2 Burp Suite中CC5 payload的注入点选择CC5不是万能钥匙它只在特定反序列化入口生效。常见可利用点HTTP Header中的Cookie如Cookie: JSESSIONIDACB123; payload...某些老版本Spring Session会反序列化Cookie值POST Body的二进制数据如Content-Type: application/x-java-serialized-object直接提交序列化对象JSON中的base64字段如{data:rO0AB...}后端用Base64.decode()后调用ObjectInputStreamXML中的CDATA块如param![CDATA[rO0AB...]]/param某些XML解析器会反序列化CDATA内容。关键判断依据看后端是否调用ObjectInputStream.readObject()且输入流来自用户可控数据。在Burp中用Decoder模块将base64 payload解码确认开头为aced0005Java序列化魔数再用Hex视图查看类名是否包含BadAttributeValueExpException。4.3 WAF/RASP绕过技巧CC5的“隐身术”当CC5被WAF拦截不要盲目换链先分析拦截规则规则1匹配BadAttributeValueExpException类名解决方案用Reflections动态加载类避免硬编码类名。例如Class clazz Class.forName(javax.management.BadAttributeValueExpException); Constructor cons clazz.getDeclaredConstructor(Object.class); cons.setAccessible(true); Object bad cons.newInstance(null);规则2匹配TemplatesImpl或TransformerFactoryImpl解决方案替换为InstantiateTransformer需配合ConstantTransformer或使用AnnotationInvocationHandler的memberValues字段直接注入TemplatesImpl绕过TemplatesImpl类名检测。规则3匹配Runtime.getRuntime().exec()字节码特征解决方案改用URLClassLoader加载远程class或用ProcessBuilder替代Runtime.exec()降低特征明显度。实操心得某次渗透中目标WAF拦截所有含TemplatesImpl的payload。我改用ysoserial.payloads.CommonsCollections6基于HashSet但目标JDK是u121CC6不稳定。最后用CC5动态类加载绕过先用CC5触发URLClassLoader加载一个轻量级loader class再由loader class加载真正的恶意class。整个过程耗时2天但成功率100%。5. CC5链的防御与加固从开发到运维的全链路防护5.1 开发侧代码层防御的三个硬性要求禁止反序列化不可信数据这是铁律。Spring Boot项目中禁用RequestBody接收Object类型强制使用String或byte[]并在Controller中明确指定反序列化逻辑PostMapping(/api/data) public ResponseEntity? processData(RequestBody String base64Data) { try { byte[] bytes Base64.getDecoder().decode(base64Data); // 白名单校验只允许特定类 if (!isValidClass(bytes)) { throw new SecurityException(Invalid class in serialized data); } ObjectInputStream ois new ObjectInputStream(new ByteArrayInputStream(bytes)); Object obj ois.readObject(); // ← 此处仍需校验 } catch (Exception e) { log.warn(Deserialization failed, e); } }使用SerialKiller或ObjectInputFilterJDK9提供ObjectInputFilter可在ObjectInputStream构造时设置ObjectInputStream ois new ObjectInputStream(inputStream) { Override protected void filterResolveObject(Object obj) throws IOException { if (obj instanceof BadAttributeValueExpException || obj instanceof TemplatesImpl) { throw new InvalidClassException(Forbidden class); } } };对于JDK8集成SerialKiller库配置白名单serialkiller whitelist classjava.lang.String/class classjava.util.HashMap/class /whitelist /serialkiller禁用危险类的反射调用在SecurityManager中限制Runtime、ProcessBuilder等类的newInstance()调用System.setSecurityManager(new SecurityManager() { Override public void checkPermission(Permission perm) { if (perm instanceof ReflectPermission suppressAccessChecks.equals(perm.getName())) { throw new SecurityException(ReflectPermission denied); } } });5.2 运维侧中间件与容器的加固清单层级配置项推荐值验证方法Tomcatcatalina.properties中org.apache.catalina.connector.REQUIRE_SECURE_COOKIEtrue访问/manager/html检查Cookie是否带Secure标志Spring Bootapplication.yml中spring.jackson.deserialization.accept-types[java.lang.String]发送含type的JSON确认返回400错误JVM启动参数-Djdk.serialFiltermaxdepth1;maxarray10000;java.lang.*;java.util.*根据业务调整用ysoserial生成payload确认ObjectInputStream抛InvalidClassExceptionDocker容器启动时--read-only挂载应用目录truedocker exec -it container sh -c touch /app/app.jar应失败注意jdk.serialFilter在JDK8u121可用但需确保ObjectInputStream使用FilteringObjectInputStream。实测发现某些老版本Spring Boot2.0.0的GenericObjectInputStream未继承该类需升级至2.1.0。5.3 监控侧CC5攻击的实时检测特征CC5攻击在网络层和应用层均有明显特征可配置ELK或Prometheus告警网络层特征HTTP POST请求中Content-Length 10000且Content-Type包含x-java-serialized-object或application/octet-stream应用层特征日志中出现BadAttributeValueExpException、LazyMap、TemplatesImpl等类名堆栈系统层特征java.lang.ProcessBuilder.start()被频繁调用且参数含/bin/sh、cmd.exe等关键字。推荐Logstash过滤规则filter { if [message] ~ /BadAttributeValueExpException|LazyMap|TemplatesImpl/ { mutate { add_tag [cc5_attack] } } if [http_content_length] 10000 and [http_content_type] ~ /x-java-serialized-object|octet-stream/ { mutate { add_tag [suspect_deserialize] } } }6. CC5链的演进与替代方案从CC5到CC11的攻防博弈6.1 CC5之后的链式进化为什么CC6、CC7、CC11相继出现CC5的生命周期约2年2015-2017随后被CC6HashSetAnnotationInvocationHandler取代原因有三CC5的BadAttributeValueExpException被JDK修复u201后readObject()增加SecurityManager检查val字段不再被信任TemplatesImpl被模块化隔离JDK9移除com.sun.org.apache.xalan包TemplatesImpl不可用WAF规则全面覆盖主流WAF厂商在2017年Q3更新规则库CC5特征码被100%识别。CC6的核心创新是利用HashSet的readObject()中对HashMap的反序列化再通过AnnotationInvocationHandler触发TemplatesImpl。它不再依赖BadAttributeValueExpException而是用HashSet作为“容器”AnnotationInvocationHandler作为“触发器”TemplatesImpl作为“执行器”形成新闭环。CC112022年提出则彻底放弃TemplatesImpl转而利用Groovy、BeanShell等脚本引擎的eval()方法绕过所有Java原生类限制。6.2 现代Java应用的反序列化防御现状当前主流防御方案已从“单链拦截”升级为“行为阻断”RASP运行时应用自我保护如OpenRASP、Contrast Security在ObjectInputStream.readObject()调用时实时分析调用栈、类加载器、反射目标对可疑行为直接阻断字节码插桩在JVM启动时注入Agent监控Unsafe.allocateInstance()、Method.invoke()等敏感API调用服务网格层防护Istio Sidecar中部署Envoy Filter对gRPC/HTTP流量中的序列化数据做协议解析拒绝非法格式。我的体会在2023年参与的某金融系统渗透测试中目标已部署OpenRASP所有CC系列链均被拦截。最终利用Fastjson的autotype功能非反序列化链绕过说明防御重心已从“链检测”转向“上下文感知”。CC5的学习价值不在于它还能打多少系统而在于它教会我们所有安全机制都是动态博弈今天的防御方案明天就是攻击者的靶心。6.3 给初学者的三条硬核建议不要死记链的步骤要吃透每个类的readObject()源码CC5的BadAttributeValueExpException、CC6的HashSet、CC7的PriorityQueue它们的readObject()方法才是真正的“开关”。花1小时读源码胜过10小时跑ysoserial。搭建自己的JDK版本矩阵环境用Docker快速启动JDK7u21、JDK8u121、JDK8u191、JDK11的Tomcat容器亲自验证每条链的存活状态。环境差异比文档描述更真实。把CC5当成Java反序列化原理的“教具”它展示了ObjectInputStream如何解析字节码、AnnotationInvocationHandler如何代理调用、ClassLoader如何影响类加载。理解这些才能看懂CVE-2022-25858这类新漏洞。最后分享一个小技巧在调试CC5时用jdbJava Debugger附加到目标进程设置断点stop in javax.management.BadAttributeValueExpException.readObject然后单步执行亲眼看着val.toString()如何一步步调用到TemplatesImpl.newTransformer()。这种“眼见为实”的体验远胜任何文字描述。