前言
Java安全中经常会提到反序列化,一个将Java对象转换为字节序列传输(或保存)并在接收字节序列后反序列化为Java对象的机制,在传输(或保存)的过程中,恶意攻击者能够将传输的字节序列替换为恶意代码进而触发反序列化漏洞。其中最经典的反序列化漏洞利用工具——ysoserial,下面就分析学习一下ysoserial中的URLDNS链,以便更好地理解反序列化漏洞。
ysoserial简单分析
下载ysoserialhttps://github.com/frohoff/ysoserial项目源码,导入IDEA,并在pom.xml中搜索mainclass,寻找程序的入口点
在程序运行之前,先设置两个参数(URLDNS “http://xxxx.dnslog.cn”),否则运行IDEA会报错。
简单分析一下 GeneratePayload 的main方法,首先会判断是否传入两个参数, 如果不是则打印帮助信息 ,是的话则对两个参数依次赋值为payloadType和command。接着实例化一个payloadClass对象,跟进一下getPayloadClass方法
getPayloadClass会返回一个反射的class对象,该class对象为GeneratePayload的class对象的包名+字符串“payloads”+我们传入的第一个参数className(也就是payloadType)
对应ysoserial中写好的URLDNS脚本,也就是说,我们传入的第一个参数是找到ysoerial的payload脚本。
后续会实例化payloadClass,并调用getObject方法,传入我们传入的第二个参数command。跟进getObject方法中,分析传入command后,程序是如何处理的。
发现最终会跳转到URLDNS中
那么以上操作就是根据传入的参数,定位到利用链的类文件中,调用该类的 getObject方法,生成并返回paylaod。
URLDNS类我们先放到一边,继续分析 GeneratePayload 中的main方法。
之后会调用 serialize方法,将URLDNS类中返回的值(生成的payload)序列化并打印输出。
URLDNS链分析
URLDNS为Java的原生类,利用效果只是触发一次DNS请求,并不会命令执行,适用于验证是否存在反序列化漏洞。
在URLDNS.java中,作者已经给出了Gadget chain
我们先看第53行代码,首先声明了一个URLStreamHandler类,因为URLStreamHandler是个抽象类,不能够被实例化,因此创建了个URLStreamHandler的子类SilentURLStreamHandler。并实现父类URLStreamHandler的抽象方法openConnection。
但为什么要重写getHostAddress方法呢,其实我们运用最简单粗暴的方法,将重写方法注释掉,对比没注释之前,有啥区别:区别在于,重写getHostAddress,在我们使用ysoserial生成payload时,不会触发payload,DNSLOG接收不到请求。即因为方法直接返回为Null,不会请求我们的hostAddress
而且在URL.java中,handler被transient关键字修饰,在序列化对象的时候,handler属性不会被序列化。意味着重写的方法并不会带进我们的payload中,这样我们在触发反序列化漏洞时,getHostAddress并没有被重写,能够正常请求我们的网址。这也是我觉得很精妙的一个地方,写ysoserial的人真是个天才!太牛*了!
然后查看HashMap类,发现HashMap实现了Serializable序列化接口
重写了readObject方法
在重写的readObject方法中,有调用putVal方法进行一个hash计算
调试,跟进putVal方法, 参数key和value都为输入的dnslog地址
继续跟进,这里注意(标重点),调用的是key.hashCode(),即HashMap的参数u,顺利的从HashMap.hash()跳转到URL.hashCode()。
这里hashCode()方法会对hashCode属性重新赋值
这里调用了 URLStreamHandler 类的hashCode方法,参数(URL)u为传入的DNSLOG地址
通过getProtocol方法,获取 协议类型——http
接下来这里进行一个if判断,h的值会加上portocal的hashCode()方法的返回值,跟进protocol.hashCode()方法
继续往下调试
最后将(String)http的hash值经过一系列的运算赋值给h。
继续跟进,调用了getHostAdress方法
查看getHostAdress,先后调用getHost()、getByName()两个方法,最终是通过getByName(getHost())去发送dnslog请求
分析到这里,dnslog就接收到了请求信息
结论
因为HashMap实现了Serializable接口,重写了readObject方法,导致在接下来调用hashCode时,能够成功通过hashCode()方法,一步一步调用URL.getHostAddress,进而发起远程请求触发dnslog。