前言
URLDNS这个gadgets得主要做用为dns外联到dnslog中,在dnslog中得到回显以此来判断目标是否存在漏洞。下面分析一下这条链得原理。
代码分析
1 | public class URLDNS implements ObjectPayload<Object> { |
先给出利用链
1 | Gadget Chain: |
然后首先看URLDNS类得getObject方法,该方法获取一个url返回一个对象,该对象进行序列化再反序列化后就会触发dns解析。该对象是HashMap类型得,因此我们可以查看该类得readObject或者readResolve之类得方法。从利用链中可以看到我们需要跟进得方法是readObject方法。
可以看到readObject中把map中得key和value分别反序列化还原了其值,然后对key执行了hash方法。跟入hash方法。
可以看到该方法在key为空时返回0,key不为空时执行(h = key.hashCode()) ^ (h >>> 16)
在跟入key得hashCode方法前我们首先需要确认一件事,就是key是哪一个类得对象。翻看ysoserial.payloads.URLDNS的代码我们发现hashmap中得key是URL类的对象。因此我们就进入URL类的hashCode方法中。
该方法首先校验了hashcode是否为-1,不是-1直接返回,是-1继续。我们这里是希望继续下去得,而看到类中对hashcode属性得定义是private int,没有加transient,因此是可以序列化得,所以我们可以在序列化之前把hashcode内容设置为-1,这样反序列后就能取到hashcode为-1。
接下的问题就是确定handle的值是什么,我们查看URL类中的handle属性,发现handle被定义为
1 | transient URLStreamHandler handler; |
这说明了handle的值不可被序列化,在反序列化后handle的值应当为null。这样岂不是空指针了?其实不然,仔细观察我们发现了URL类中存在一个readResolve方法。动态调试一番之后发现readResolve其实就是调用了一个new URL(null,url,null)。
与我们之前类实例化相比较一下
1 | new URL(null,url,null) //反序列化readResolve |
两者相比就是handle刚开始的值为空了,那么我们接着调试,看最后handle的值是多少。
在构造方法中一直前进到599行,这里终于要开始获取handle的对象了
跟入方法中
handlers是一个hashtable,从中去出了我们http对应的handle对象,然后向上转型给URLStreamHandler类。接下来handle!=null因此直接return。ok,到这里为止理清楚了handle的值到底是什么,继续之前的handler.hashCode(this)。
此时的handle是URLStreamHandler类的实例,直接去看URLStreamHandler的hashCode(URL)方法。
进入getHostAddress方法
两个箭头,一个是获取我们的host也就是dnslog的网址。另外一个是对host进行解析,到此出发dnslog。
到这里结束了?不,还未结束!!!作者的代码我们真的理解清楚了吗?如果真的理解清楚了,请尝试回答下面几个问题。
问题1:请问作者为什么会在代码中给出如下代码
1 | static class SilentURLStreamHandler extends URLStreamHandler { |
这个问题很好理解,因为URLStreamHandler是一个抽象函数,我们不能直接实例化作为参数赋给URL类,所以我们写了一个类SilentURLStreamHandler继承了URLStreamHandler。而这里我们自己写的类不用担心在目标机器不存在而反序列化不了的问题,因为前面分析过handle是不能被反序列化的,其值是在反序列化过程中被重新赋予的。
这个问题解决完后还有另外一个问题。
问题2:URLStreamHandler抽象类的抽象函数只有openConnection,那么我只需要重写openConnection就行了,为什么还要重写getHostAddress(URL u)这个方法?
解决这个问题,我们还是先看代码。
这里URL类作为hashmap的key是被put进去的,跟进看put方法,发现存在 hash(key) 这段代码,这段代码正是我们反序列化中触发点。所以dns解析会在put这里直接提前触发,而作者重写的handle在执行解析函数getHostAddress(URL u)是直接return了null。那么在put时就不会提前解析了。
附加题:
还能怎么绕过hashmap put时触发问题?下面是我的绕过代码,至于为什么,解析了这么多,大家伙自己体会吧~~
1 | package ysoserial; |
后记
本篇是创建博客后的第一篇文章,纪念意义浓厚,不仅是纪念了博客的初创,更重要的是本篇代表个人对于研究方面更严格的自我要求。之所以这么说是因为我在第一次写URLDNS gadgets时大致扫了一遍利用链之后就直接开写,最后在put时就触发了dnslog却没发现这个问题以为自己写好了,当时为自己这么轻松的就写出了payload还洋洋自得。过了段时间再看了一遍才发现自己弄错了,并且作者其实在源码地方特别是继承抽象类的部分特意加了很多注释,意思就是要绕过put出发点,顿时感觉羞愧不已。以此类推,那以前究竟有多少因为研究不仔细、不透彻忽略掉的细节呢?之前部门同事聊天,我们得出了同一个结论,那就是:大佬渗透工程师(研究员)和普通渗透工程师(研究员)之间得区别在于,对于细节得钻研。你看不起得ssrf,大佬就能打到内网权限。你看不起得xxe,大佬就能用xxe拿打域。还有多少例子深刻反映了这一现象,希望从这篇文章起,在大家得监督下成为一个严谨、务实得肾透攻城湿。