最近看了些java反序列化的文章,感觉都不是太有逻辑,写个文章边记录边整理。估计内容太多,可能要写几篇。这篇先写单纯的反序列化,不涉及rmi/jndi等内容。
和python、php类似,java也有序列化和反序列化的功能,自然也存在类似的安全问题。将对象序列化其实就是将对象转换为字节序列,方便保存和传输。先写一个正常的序列化和反序列化过程看看。
首先定义一个类,这个类需要继承Serializable才能被反序列化。这个接口其实是空的,只是相当于一个标记。然后需要定义serialVersionUID,否则可能会因为序列化的版本号不同报错。
1 | import java.io.Serializable; |
然后定义序列化的函数,其实就是调用ObjectOutputStream的writeObject方法,将传入的对象保存到文件。文件开头是16进制aced0005
1 | public class serial { |
反序列化的函数,其实就是调用ObjectInputStream的readObject方法,从文件中读取对象。反序列化创建对象的过程是和构造函数无关的,即使修改构造函数也不会改变生成的类的内容。
1 | public static void unser() { |
其实和别的语言差不多,核心就是writeObject和readObject两个方法(感觉相当于php的serialize和unserialize)。而反序列化过程其实只调用了readObject这一个函数。所以利用方法也就是找重写了这个函数的类,再调用重写过的readObject方法(有点像python反序列化的reduce)。比如给上面的person类重写个弹计算器的readObject
1 | class Person implements Serializable { |
这样反序列化的时候会执行重写后的readObject,弹个计算器。
当然这只是观察重写readObject会有什么效果,真实环境不会有这样的代码。但有可能有某个类重写了readObject方法,被反序列化的时候实现了某些意料之外的效果。
分析个实例,p神推荐的URLDNS利用链,来自ysoserial。首先是漏洞代码vul.java:
1 | import java.io.FileInputStream; |
其实这行代码就相当于php中的unserialize(file_get_contents(“out.bin”)),非常直白。
然后分析下生成序列化文件的代码,这里的效果是在反序列化时实现一个dns请求,类似一个盲ssrf的效果。
1 | public static void main(String[] args) throws Exception { |
分析下原理:这段代码最后传到writeObject的是一个HashMap类型的对象,所以肯定是HashMap的readObject重写了,跟进去看一下:
1 | private void readObject(java.io.ObjectInputStream s) |
根据ysoserial的注释,是hash操作触发了DNS,所以跟一下最后一行的hash函数
1 | static final int hash(Object key) { |
这个hash函数调用了key的hashCode方法,这里的key是URL类型的对象,而这个类正好有个hashCode方法,所以这里调用的就不是HashMap类的hashCode而是URL的hashCode了,长这样:
1 | public synchronized int hashCode() { |
hashCode等于-1的时候调用handler.hashCode,再看看handler的hashCode是什么:
1 | protected int hashCode(URL u) { |
中间的InetAddress addr = getHostAddress(u)这里调用了getHostAddress方法,这个方法一看名字就是发起了DNS请求,分析到此结束。
可以发现其实利用方法和php的差不多,也是利用不同类同名的方法。调用链就是HashMap.readObject()->HashMap.putVal()->HashMap.hash()->URL.hashCode()
参考链接
https://xz.aliyun.com/t/7157
https://www.heibai.org/post/1529.html