java反序列化之URLDNS链
java反序列化之URLDNS链
java反序列化
在 java 中,如果一个对象实现了 Serializable 接口就可以被序列化,java 的这种序列化模式为开发者提供了很多便利,我们可以不必关心具体序列化的过程,只要这个类实现了 Serilizable 接口,这个类的所有属性和方法都会自动序列化,transient 关键字修饰的属性除外,不参与序列化过程。
URLDNS链
这条利用链有一下特点:
- 不限制 jdk 版本,使用 java 内部类,对第三方依赖没用要求
- 目标无回显,可以通过 DNS 请求来验证是否存在反序列化漏洞
- URLDNS链只能发起 DNS 请求,并不能进行其他利用。虽然没有任何攻击性,但是在渗透测试的过程中能进行无回显验证
在 ysoserial 中列出的调用链:
1 | * Gadget Chain: |
原理
java.util.HashMap重写了readObject方法,在反序列化时会调用hash函数计算 key 的 hashCode。而java.net.URL的 hashCode 在计算时会调用getHostAddress来解析域名,从而发出 DNS 请求。
定位到 HashMap#readObject 方法:
1 | private void readObject(ObjectInputStream s) |
这里我们只需要关注putVal方法,这是一个向 HashMap 中存放键值对的方法,这里调用了hash方法来处理了 key,定位到hash方法:
1 | static final int hash(Object key) { |
可以看到,这里调用了key.hashCode()来计算 key 的哈希值。
在URLDNS利用链中,我们构造的 HashMap 里放的第一个参数就是一个URL对象,所以把 key 用URL对象替换,即调用URL对象的 hashCode方法。
样例:
1 | HashMap<URL,Integer> hashmap=new HashMap<URL,Integer>(); |
定位到 URL#hashCode 方法:
1 | public synchronized int hashCode() { |
这里的synchronized关键字修饰的方法为同步方法。当synchronized方法执行完或发生异常时,会自动释放锁。
这里可以看到调用了handler.hashCode(this),定位到handler:
1 | transient URLStreamHandler handler; |
这是一个URLStreamHandler对象,那我们定位到这个类的hashCode方法:
1 | protected int hashCode(URL u) { |
InetAddress addr = getHostAddress(u);这里调用了getHostAddress方法,来看一下是怎么实现的:
1 | synchronized InetAddress getHostAddress() { |
getHostAddress()这个方法已经是调用链的最底层了,hostAddress = InetAddress.getByName(host)是唯一出口,它的内部实现最终会调用一个 Native 方法(由c/cpp实现的底层系统函数),这个 Native 方法会直接调用操作系统的 Socket API,发起 DNS 请求。
至此,从正面的分析结束。
回到最开始的 HashMap#readObject:
1 | for (int i = 0; i < mappings; i++) { |
可以看到,这里的 key 是从K key = (K) s.readObject();中经过 readObject 得来的,这说明在这之前必定会有 writeObject 方法来进行序列化。定位到 HashMap#writeObject:
1 | private void writeObject(java.io.ObjectOutputStream s) |
这里调用了internalWriteEntries,定位到它:
1 | void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException { |
这里的 key 以及 value 是从 table 中取的(table是 HashMap 类的一个成员变量,是整个哈希表的底层存储容器),具体实现:transient Node<K,V>[] table;
而想要修改table的值,就需要调用 HashMap#put 方法,定位到它:
1 | public V put(K key, V value) { |
这里也调用了hash方法,根据上面所分析的调用链即可发现这里就会产生第一次 DNS 请求。
这里我们仔细思考一下,在第一次put一个URL对象时,会调用这个URL对象的 hashCode 方法,这样我们的hashCode 属性就不为-1,同时,由于我们是在本地构造的,所以在第一次发起 DNS 请求时是由我们本地发起的,并不是由服务器发起的。同时,由于此时我们URL对象的 hashCode 属性已经不为-1,即使上传到服务器上,也不会进行 DNS 请求。
为了避免由我们本地发起的请求被我们误认为是服务器发起的,我们需要用反射进行修改 hashCode 属性,这里给一下样例:
1 | HashMap<URL,Integer> hashMap = new HashMap<>(); |
这样我们序列化之后得到的 hashCode 属性还是-1,当我们上传到服务器上时,反序列化就会进行 put 操作,从而发起 DNS 请求。
现在来总结一下 URLDNS 链的调用过程,我们在本地构造一个 HashMap 对象,序列化之后上传到服务器,服务器在反序列化时一定会触发 HashMap 的 readObject 方法(因为在 JVM 中,ois.readObject()发现这是一个 HashMap 对象后,会新建一个 HashMap 对象并调用它的 readObject 方法来反序列化我们上传的字节流),然后触发 hash 方法中的 key 的 hashCode 方法,也就是 URL 对象的 hashCode 方法,在 URL 对象的 hashCode 方法中会触发一个 URLStreamHandler 对象的 hashCode 方法,从而触发内部的 getHostAddress 方法,发起 DNS 请求。
总结
URLDNS链是 java 反序列化中非常基础的调用链,这里我花了将近两个晚上来彻底弄明白它的原理,其实是有点慢的,但是我觉得慢没关系,搞懂才是最重要的,希望以后在学习其他调用链时能够更加得心应手。
- Title: java反序列化之URLDNS链
- Author: SoloWalker
- Created at : 2026-04-21 00:00:00
- Updated at : 2026-04-23 20:54:33
- Link: https://s0lowalker.github.io/2026/04/21/java反序列化之URLDNS链/
- License: This work is licensed under CC BY-NC-SA 4.0.