什么是ysoserial
ysoserial是一款Java反序列化利用工具,可以理解为payload生成器,而Java反序列化中的payload通常是一条利用链,叫做gadget chain,这就和PHP 反序列化构造利用链是类似的道理,如果了解过PHP 反序列化的话相信不是太难理解利用链的概念,再抽象一点,就像在Flask+jinja2模板注入中,通过变量到基类,然后从基类到子类一步一步找到执行系统命令/文件读取的函数,这也算得上是一种利用链。
ysoserial地址:https://github.com/frohoff/ysoserial (salute!
在ysoserial根据组件或者环境不同内置了数条利用链,其中urldns这条利用链使用的组件是Java原生的类与方法,应该适用于所有情况。
urldns链生成的payload执行后会产生一个dns query,如果在dnslog平台看到了该请求则可以认为存在Java反序列化漏洞。
部分Java基础知识
在讲具体代码之前还需要说一下部分用到的Java知识
URLStreamHandler类
抽象类URLStreamHandler是所有流协议处理程序的通用父类。流协议处理程序知道如何为特定协议类型(如http、ftp或gopher)建立连接。
在大多数情况下,URLStreamHandler子类的实例不是由应用程序直接创建的。相反,在构造URL时第一次遇到协议名时,会自动加载相应的流协议处理程序。
URL类
可以理解为通过url类把一个字符串url处理成Java可以理解的url格式。
HashMap类
Java中的Map是一个接口,不能实例化Map类,而是使用implemented Map的类,如hashmap,treemap。
map是key-value键值对,其中key是不可重复的,hashmap使用哈希算法对key去重,效率更高。
hashmap.put()
hashmap.put()方法将一个k,v(key,value)放入hashmap实例中。
Field类
Field 是一个类,位于 java.lang.reflect 包下。在 java 反射中 Field 类描述的是类的属性,功能包括:
- 获取当前对象的成员变量的类型
- 对成员变量重新设值
field.setAccessible(true)
正常 set(Object obj, Object value) 时,修改 final 类型的变量会导致 IllegalAccessException。由于 Field 继承自 AccessibleObject,我们可以使用 AccessibleObject.setAccessible() 方法告诉安全机制,这个变量可以访问即可解决,如 field.setAccessible(true)。
getField
- getField() 方法,获取一个类的 public 成员变量,包括基类
- getDeclaredField() 方法,获取一个类的 所有成员变量,不包括基类
set()
Field类的set方法其实就有点反射的意思了,首先需要通过getDeclaredField方法获取某个属性,如
1 | Field hashcode=u.getClass().getDeclaredField("hashCode"); |
然后将hashcode设置为可访问
1 | hashcode.setAccessible(true); |
然后将hashcode的属性值设为某个值:
1 | hashcode.set(u,-1); |
在这个过程中我认为有点invoke的感觉,正常设置某个属性的值应该是u.hashcode=-1。
使用reflection的话,通过**Reflections.setFieldValue(u, “hashCode”, -1);**也可以修改hashcode的值。
payload生成分析
1 | package ysoserial.test; |
在**ht.put(u,url)处下断点调试,首先跟到的是putval()**方法
接下来跟到了HashMap.java的hash()方法,发现调用了hashcode()方法,继续跟进hashcode()方法发现到了URL.JAVA的hashcode()方法中
因为handler是URLStreamHandler,所以后面需要调用URLStreamHandler类的**hashCode()**方法
确实到了URLStreamHandler类的**hashCode()方法中,我们重点关注getHostAddress()**方法
发现进入getHostAddress()方法后调用了getHost()和getByName()方法,通过查阅文档,发现InetAddress.getByName方法是用来获取主机ip地址的,如果是一个url,则会进行一次dns查询。当前dnslog解析记录为空
执行完这句话之后可以看到了dnslog上收到了解析记录。
那么到此为止payload生成就结束了,调用链为
1 | HashMap.readObject() -> HashMap.putVal() -> HashMap.hash() |
但是在这里只是生成payload,并不希望发送真正的dnslog解析请求,所以需要上面代码注释的部分,重写URLStreamHandler的getHostAddress()方法,使得exp只生成payload而不发送dnslog解析请求。我们这一步的目的是生成payload且不发送dns解析请求所以重写了上述方法,而生成payload打过去之后目标服务肯定不会重写这个方法。
在payload生成完之后通过输出流写到文件中,至此payload生成分析结束。
payload反序列化调用分析
1 | package ysoserial.test; |
重点看最后两行代码,在Java中使用readobject方法反序列化对象,在hashmap中也下断点调试一下
在跟到putval的时候,
发现后面会调用到在**u.hostAddress = InetAddress.getByName(host);**下的断点,跟进去就可以看到下一步就是进行域名解析了
后面dnslog平台也受到了解析请求