URLDNS 是适合新手分析的反序列化链,只依赖原生类,没有 jdk 版本限制,也被 ysoserial 涵盖在其中。它不会执行命令,只会触发 DNS 解析,因此通常用来探测是否存在反序列化漏洞。
背景描述
漏洞来源地址:点击这里
Java URLDNS链是通过readObject
反序列化+DNS查询来确认反序列化利用点的存在。该利用链具有如下特点:
- 只能发起 DNS 请求,不能进行其它利用。
- 不限制 jdk 版本,使用 Java 内置类,对第三方依赖没有要求。
- 目标无回显,可以通过 DNS 请求来验证是否存在反序列化漏洞。
JAVA环境
java version "1.8.0_441"
Java(TM) SE Runtime Environment (build 1.8.0_441-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.441-b07, mixed mode)
sink点 - sink
这个漏洞关键点是 Java 内置的 java.net.URL
类,这个类的 equals
和 hashCode
方法具有一个有趣的特性:在对 URL 对象进行比较时(使用 equals
/ hashCode
方法),会触发一次 DNS 解析,因为对于 URL 来说,如果两个主机名(host)都可以解析为相同的 IP 地址,则这两个主机会被认为是相同的。
URL#equals
我们先来看一下这个特性的具体调用代码,URL#equals
方法重写了 Object 的判断,调用 URLStreamHandler#equals
方法进行判断:
public boolean equals(Object obj) {
if (!(obj instanceof URL))
return false;
URL u2 = (URL)obj;
return handler.equals(this, u2);
}
URLStreamHandler#equals
方法判断 URL 对象的锚点是否相同,并调用 sameFile
方法比较两个 URL
protected boolean equals(URL u1, URL u2) {
String ref1 = u1.getRef();
String ref2 = u2.getRef();
return (ref1 == ref2 || (ref1 != null && ref1.equals(ref2))) &&
sameFile(u1, u2);
}
sameFile
方法会检查是否引用了相同的 protocol(协议)、host(主机)、port(端口)、path(路径),在比较 host(主机) 时,调用 hostsEqual
方法进行比较。
protected boolean sameFile(URL u1, URL u2) {
// Compare the protocols.
if (!((u1.getProtocol() == u2.getProtocol()) ||
(u1.getProtocol() != null &&
u1.getProtocol().equalsIgnoreCase(u2.getProtocol()))))
return false;
// Compare the files.
if (!(u1.getFile() == u2.getFile() ||
(u1.getFile() != null && u1.getFile().equals(u2.getFile()))))
return false;
// Compare the ports.
int port1, port2;
port1 = (u1.getPort() != -1) ? u1.getPort() : u1.handler.getDefaultPort();
port2 = (u2.getPort() != -1) ? u2.getPort() : u2.handler.getDefaultPort();
if (port1 != port2)
return false;
// Compare the hosts.
if (!hostsEqual(u1, u2))
return false;
return true;
}
hostsEqual
方法调用 getHostAddress
方法对要比较的两个 URL 进行请求解析 IP 地址,并实施对比。
protected boolean hostsEqual(URL u1, URL u2) {
InetAddress a1 = getHostAddress(u1);
InetAddress a2 = getHostAddress(u2);
// if we have internet address for both, compare them
if (a1 != null && a2 != null) {
return a1.equals(a2);
// else, if both have host names, compare them
} else if (u1.getHost() != null && u2.getHost() != null)
return u1.getHost().equalsIgnoreCase(u2.getHost());
else
return u1.getHost() == null && u2.getHost() == null;
}
getHostAddress
方法使用 InetAddress.getByName()
方法对 host 进行解析,触发了 DNS 请求。
synchronized InetAddress getHostAddress() {
if (hostAddress != null) {
return hostAddress;
}
if (host == null || host.isEmpty()) {
return null;
}
try {
hostAddress = InetAddress.getByName(host);
} catch (UnknownHostException | SecurityExcept