Java 反序列化第一条链 URLDNS
0x01 意义
URLDNS 就是ysoserial中⼀个利⽤链的名字,但准确来说,这个其实不能称作“利⽤链”。因为其参数不 是⼀个可以“利⽤”的命令,⽽仅为⼀个URL,其能触发的结果也不是命令执⾏,⽽是⼀次DNS请求。 虽然这个“利⽤链”实际上是不能“利⽤”的,但因为其如下的优点,⾮常适合我们在检测反序列化漏洞时 使⽤: 使⽤Java内置的类构造,对第三⽅库没有依赖 在⽬标没有回显的时候,能够通过DNS请求得知是否存在反序列化漏洞
既然提到了 ysoserial 那就简单了解一下吧,2015年Gabriel Lawrence (@gebl)和Chris Frohoff (@frohoff)在AppSecCali上提出了利⽤Apache Commons Collections来构造命令执⾏的利⽤ 链,并在年底因为对Weblogic、JBoss、Jenkins等著名应⽤的利⽤⽽ysoserial就是两位原作者在此议题中释出的⼀个⼯具,它可以让⽤户根据⾃⼰选择的利⽤链,⽣成反 序列化利⽤数据,通过将这些数据发送给⽬标,从⽽执⾏⽤户预先定义的命令(转载自 p 神
有了 ysoserial ,我们就能很方便的产生构造链,下面我们就来探讨 URLDNS 链的实现原理。
0x02 调试
来看看 ysoserial 是怎么实现的吧,先去 yakit 产生一个可用域名,然后命令行生成 payload。
1 | java -jar ysoserial.jar URLDNS "http://nwmenmnays.dgrh3.cn" |
注意这里生成 urldns.bin 文件得在 kali 下完成,windows 下的话后面反序列化调试会报错。
得到后编写测试代码
1 | public class URLDNSTest { |
执行代码可以发现 yakit 已经收到了 dns 查询,下面动态调试看一下执行流程。
我们可以看到 ysoserial 源码已经给出了利用链
1 | * Gadget Chain: |
我们在这几处关键代码处打下断点,不能一下全打,因为 hash() 这个函数调用的太频繁了,干扰太多。打一个执行一下,先把 HashMap.putVal 断点打上。
1 | putVal(hash(key), key, value, false, false); |
可以看到 key 值就是我们的 url 参数

然后进入 hash 函数
1 | static final int hash(Object key) { |
这里不要直接进 **hashCode()**,会进到 Object 里的。直接 f7 步进,来到 URL 类的 hashcode 函数下
1 | public synchronized int hashCode() { |
hashcode 值为 -1

进入 handler.hashCode 函数,注意到 359 行
1 | InetAddress addr = getHostAddress(u); |
再次跟进 getHostAddress 方法
1 | protected synchronized InetAddress getHostAddress(URL u) { |
这里 InetAddress.getByName(host) 的作用是根据主机名,获取其IP地址,在网络上其实就是⼀次 DNS查询。到这里就不必要再跟了,yakit 也收到 dns 查询了。
0x03 重写
现在我们自己来实现,先梳理一下反序列化流程
1 | HashMap->readObject() |
先把 HashMap 和 url 对象创建出来
1 | HashMap ht = new HashMap(); |
看 yso 源码
1 | ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup. |
我们也模仿着写,put 键值对
1 | ht.put(u, url); |
然后序列化
1 | import java.io.*; |
序列化发现直接发出 dns 查询了,o.O? 动态看看什么情况。
原来是 ht.put 触发了 hash ,又走了一遍 putVal 到 getHostAddress 的流程
1 | public V put(K key, V value) { |
这样我们可以在 ht.put(u, url); 之前利用反射来使 URL 类的私有变量 hashcode 值不为 -1(默认初始值为 -1),这样就不会进入 handler.hashCode 了。
1 | Class clazz = u.getClass(); |
再序列化就不会触发了,但是反序列话也不会触发,O.o? 反序列化动调看看,原来是我们之前设置 hashCode 变量为 1 然后直接走
1 | if (hashCode != -1) |
所以这里 hashCode 仍然走的是这个逻辑,那我们想让他进入
1 | hashCode = handler.hashCode(this); |
就只能再利用反射在 ht.put(u, url) 之后重新设置 hashCode 为 -1
1 | field.set(u, 1); |
成功只在反序列化阶段触发 dns 查询

完整代码
1 | import java.io.*; |
0x04 思考
复现完不禁会想,发现这条链的大佬是怎么想到这条链的?
利用 HashMap 类作为接口是因为其实现了Serializable接口,重写了readObject,重写的 readObject 调用 hash 函数计算 key 的hashCode,而java.net.URL的hashCode在计算时会调用 getHostAddress 来解析域名, 从而发出 DNS 请求。
可以看到从 HashMap 到 URL 关键在于 hashCode 方法,这个方法点击的话跳转的是 Object 类,也就是说很多类都会有 hashCode方法,为什么偏偏挑选 HashMap 呢?我觉得 HashMap 调用了 hashcode 方法倒是其次,最主要的是它接受的参数类型没有限制,所以我们才能传 key 为 URL ,进而来到 URL 的 hashCode 方法里来触发 DNS 查询。