关于URLDNS利用链的学习
JAVA URLDNS利用链分析
一、概述
URLDNS是ysoserial项目中的一条利用链,这条利用链主要用来做验证反序列化漏洞是否存在。
URLDNS利用链有以下特点:
1 | 1、利用链只能发起DNS请求,不能进行其他的利用 |
利用URLDNS链验证反序列化漏洞存在的原理:
java.until.hashmap类实现了serializable接口,重写了read0bject方法,再反序列化时会调用hash函数计算key的hashcode,
java.net.URL的hashcode再计算时会调用getHostAddress
来解析域名, 从而发出DNS
请求。
那如何利用这个特点去验证是否存在反序列化漏洞?
我们可以构造一个恶意的序列华对象,序列化对象中包含一个恶意的URL地址,当目标将序列化对象进行反序列化时,会调用hash函数计算key的hashcode,然后java.net.URL的hashcode再计算时会调用getHostAddress
来解析域名,解析之后回想我们定义的url地址发送请求,从而我们可以验证是否存在反序列化请求。
二、URLDNS利用链分析
在上面我们简单介绍了以下URLDNS链的作用和工作原理,下面我们通过ysoserial项目来相信分析以下利用链的实现过程。
项目地址:https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial
我们先看一下URLDNS.java代码:
1 |
|
可以看到在这里实例化了一个Hashmap类
我们先了解以下hashmap类:
hashmap是一种常用的集合类,它实现了map接口,可以储存键值对。hashmap使用哈希表来实现,提供了快速的插入,删除和查找工作。
下面是hashmap常用的一些方法:
1 | put(key, value):将指定的键值对存储到HashMap中,如果key已经存在,则会用新的value覆盖旧的value,返回值为旧的 value或null。 |
然后上面说实例化了一个HashMap类,这里因为Hashmap类重写了read0bject()方法。
跟进hashmap类的read0bject()方法(这里只给出主要部分)
1 | private void readObject(java.io.ObjectInputStream s) |
可以看到这里有一个循环,先分析一下这个循环:
1 | @SuppressWarnings("unchecked")是Java中的一个注解,用于告诉编译器忽略在代码中出现的类型转换警告。在Java中,有些类型转换可能会导致运行时异常,例如ClassCastException,因此编译器会在代码中出现类型转换时发出警告。 |
其实整体来看就是通过遍历读取序列化后的键和值并将其反序列化后重新组成键值对
继续跟进hash(key)方法:
1 | static final int hash(Object key) { |
这里key!=null就会执行h = key.hashCode()
到这里我们就可以找那些可以序列化的类中有hashcode方法(从触发点倒着分析这条利用链可能更好分析一点)。
重新回到urldns.java类分析
可以看到首先实例化了一个URLStreamHandler类,然后又实例化了hashmap和URL类
然后是ht.put(u,url)向名为 ht
的 Hashtable
对象中添加一个新的键值对,其中 u
是键,url
是值。
跟进一下put方法:
可以看到这里调用了purval函数作为返回值
这里的key就是我们上面的url对,然后这里的key是作为hash()方法的参数,那我们继续跟进hash()方法:
跟进到hash方法后我们可以看到当key值就是我们的url对象不为null的时候会调用hashcode方法,这里的key就是url类的实例化对象。
在hash方法中,会调用key的hashCode方法,也就是说,通过创建一个HashMap对象,对该对象的key传入其他任意对象,再对HashMap实例进行序列化,再将其进行反序列化时,就会触发执行任意对象hashCode方法。
跟入查看URL类的hashcode()方法:
可以看到hashcode!=-1会调用 handler的 hashCode()
方法
然后这里handler是URLStreamHandler
的实例对象。
跟进一些URLStreamHandler
的hashcode方法:
可以看到hashcode方法中调用了getHostAddress,传入的是url对象
然后返回url对象调用getHostAddress方法。
继续跟进getHostAddress方法:
继续跟进:
这里就是我们这条利用链的最终触发点了,
InetAddress.getByName()
是Java中的一个方法,它将主机名或IP地址字符串作为参数,并返回一个表示其网络地址的 InetAddress
对象。如果参数是主机名,getByName()
方法将查询DNS以查找该主机名的IP地址。如果参数是IP地址字符串,则该方法将返回一个表示该IP地址的 InetAddress
对象。该方法可能会抛出 UnknownHostException
异常,如果主机名无法解析或IP地址无效,则会抛出该异常。
这里根据主机名获取ip地址会进行一次DNS查询
这里进行一个小demo吧:
1 | package ysoserial.test2; |
测试结果:
可以看到对域名进行了一次解析,那么我们就可以通过这里来验证是否存在反序列化漏洞。
那么经过上面的分析,我们可以得到一个利用链:
1 | HashMap->readObject |
整体来说就是:
1 | HashMap.readObject() |
经过上面的分析我们对urldns利用链有了一定的了解,但是大家这里就会有一个疑问,既然最终调用的是url类里面的hashcode,为什么还要从hashmap类入手呢?
这是因为在url类里面的readobject里面并没有调用hashcode,所以我们想要使用这条链就要找到一个在反序列化过程中调用了hashcode的类,而使用hashcode方法最多的自然是hashmap结构,而我们进入hashmap类的源码可以看到,其readObject方法在最后调用了hash方法:
跟进hash方法:
可以看到这里调用了url类的hashcode方法,这也是我们为什么要从hashmap类入手的原因
上面我们从正面分析了从hashmap到url类的hashcode是如何触发dns解析
回到hashmap的readobject方法:
1 | for (int i = 0; i < mappings; i++) { |
key 是从K key = (K) s.readObject();
这段代码,也是就是readObject中得到的,说明之前在writeObject会写入key
进入到hashmapwriteObject
可以看到这里最后调用了internalWriteEntries方法
跟进:
可以看到这里的key和value都是从tables中获取的,而table的值就是hashmap中的table的值
那我们想要修改table的值就需要调用hashmap中的put方法
但是hashmap中的put方法也会对key调用一次hash方法,所以在这里会产生第一次dns查询
1 | public V put(K key, V value) { |
为了避免这一次的dns查询,ysoserial 中使用SilentURLStreamHandler
方法,直接返回null,并不会像URLStreamHandler
那样去调用一系列方法最终到getByName
,因此也就不会触发dns查询了
或者在生成paylaod的时候将hashcode的值设为除-1外其他的.
通过上面的分析我们自己来写一条pop链:
1 | import java.io.*; |
运行结果:
这里通过dnslog来进行测试
运行代码后:
- 标题: 关于URLDNS利用链的学习
- 作者: GTL-JU
- 创建于: 2023-07-15 09:18:04
- 更新于: 2023-07-15 09:19:14
- 链接: https://gtl-ju.github.io/2023/07/15/URLDNS/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。