JAVA序列化与反序列化
为什么需要序列化
java的序列化机制就是为了持久化存储某个对象或者在网络上传输某个对象,一旦jvm关闭,那么java中的类,对象也就销毁了,所以要想保存它,就需要把他转换为字节序列写到某个文件或是其它哪里。
序列化与反序列化
Java 序列化是指把 Java 对象转换为字节序列的过程
ObjectOutputStream类的 writeObject() 方法可以实现序列化
Java 反序列化是指把字节序列恢复为 Java 对象的过程
ObjectInputStream 类的 readObject() 方法用于反序列化。
要实现序列化,类必须实现java.io.Serializable
接口,并且所有的属性都必须是可序列化的。
实例
1 | //Name.java |
1 | //Main.java |
可以通过xxd命令看一下我们序列化的数据
中间哪一行是16进制内容,最右侧是字符显示,注意看前面那个aced 0005
- 前面的
aced
:STREAM_MAGIC,声明使用了序列化协议,可以通过这个知道是不是序列化内容。 - 后面的
0005
:STREAM_VERSION,序列化协议版本。
然后通过修改age的修饰符,在运行一遍。
1 | public static int age =25 |
可以看到age没有被序列化,所以不止是transient关键字修饰的属性,static关键字修饰的属性也不会被序列化。
对比php反序列化
php反序列化一个对象时会自动触发__weakup
、__destruct
这些函数,如果这两个函数中有敏感操作,那么就可能出现漏洞。那么在java中是不是也有自动触发的函数呢,其实就是readobject()
函数。如果我们改写readobject()
函数,并且readobject()
有危险操作就会导致反序列化漏洞。
修改Name.java
,然后再次运行,可以看到readObject
函数运行,并弹出计算器。
调试
将下载来的文件打包好,生成.jar文件,然后执行命令,比如这里我想调试URLDNS,可以用网上的dnslog平台
1 | java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS http://foh1xpxj.ns.dns3.cf >url.bin |
然后测试一下
1 | package Test; |
URLDNS分析
可以在这里下载源码ysoserial/URLDNS.java at master · frohoff/ysoserial · GitHub
1 | package ysoserial.payloads; |
URLDNS的getobject方法是最后生成payload的方法,然后我们可以看到这个方法返回了一个类,而这个类就是最后被序列化的对象,而这个类,我们往前看,可以发现他在这个方法中是HashMap。但是我们知道java的反序列化都是readobject()方法开始的。所以我们回溯一下这个HashMap类的readobject方法。.
1 | private void readObject(java.io.ObjectInputStream s) |
从上面的注释中一句话
1 | “During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered. 在上述放置期间,将计算并缓存URL的hashCode。如此重置下次调用hashCode时将触发DNS查找。所以应该是hashcode的操作导致了一系列问题。 |
这里的最后一行,计算了hashmap的键的hash值
1 | putVal(hash(key), key, value, false, false); |
所以我们可以在这里打断点调试一下。
看到上面的putval()函数中使用了hash()方法,我们回溯一下这个函数
1 | static final int hash(Object key) { |
它调用了一次hashcode()方法,在回溯一下
1 | public synchronized int hashCode() { |
在这中间,hashCode=handler.hashcode(this)
,在往前看,回溯这个handler的hashcode函数
1 | protected int hashCode(URL u) { |
在往下回溯这个getHostAddress
1 | protected synchronized InetAddress getHostAddress(URL u) { |
在这里u.hostAddress = InetAddress.getByName(host);
是根据主机名获取ip地址的。
所以我们可以得出这个漏洞的调用链
1 | hashmap->readobject() |