Java反序列化学习_URLDNS
反序列化基础
由于之前并没有接触过java反序列化相关知识,所以在学习的过程中遇到n个不懂得地方。所以在写之前先可能的盲点说一下
Serializable与Externalizable
这是java中的两个接口,只有继承了这两个接口中一个才能将对应的类进行序列化操作。
其中Serializable这个接口中并没有任何方法,而仅仅代表着继承了这个接口的类可以被序列化操作。
而Externalizable则有两个需要实现的方法,分别为
- readExternal:指定如何反序列化对象,即如何从字节流中还原对象的字段值。
- writeExternal:指定哪些字段需要被序列化,而不是自动序列化所有字段。
相比起Serializable,这个接口更加灵活,但是在使用时也会更加复杂。而Serializable则是使用起来更加简单且更方便维护。
readObject&&writeObject
这两个方法分别属于ObjectInput/OutputStream,通过继承了两个Object类中的接口来实现对应的功能。
这两个类是java中用来将对象和字节进行转换操作的类,但是在HashMap这个类中,HashMap实现了自己的readObject和writeObject。
一开始我搜索这两个函数时发现他们并不是属于HashMap的方法,所以就把它们理解成其他类或者接口中的函数。但是当我跟进到HashMap中时发现我所在的方法时readObject,这个方法中又调用了readObject直接就给我整蒙了。后面了专门学习了一下这里的继承和实现的关系,其实也就是HashMap为了处理自己的数据重新写了自己的序列化和反序列化的方法。
更多相关知识点:HashMap_的readObject&&writeObject
反序列化过程
这个过程不就是简单的调用readObject方法然后跳转到HashMap吗?
这是我刚开始看的时候的想法,但是事情远没有我想像的那么简单,光是看它读取前两个字节码就花了我不少时间(主要还是俺太菜了)。与php的反序列化不同java并没有将反序列化这个过程直接隐藏实现,更像是留给了开发者很大的操作空间,所以就会让人看起来很迷糊。其实归根结底就是识别对应的字节码,然后根据字节码对应的属性重建对象。
这里如果想仔细了解一下java的这些字节码,推荐使用SerializationDumper-master,但是还是建议先自己序列化一个简单的对象,然后观察要不然直接看HashMap的数据太难了。
1 |
|
具体的过程就是这样,如果想要了解更多细节,可以自行去跟进一下。
URLDNS
HashMap前置
学了这么久的前置知识总算可以开始正式跟进链子了,接下来的过程就和php中的反序列化差不多了,只要对逻辑填空即可。虽然并不需要过深的了解hashmap得实现原理,但是也大概的看一下有个底吧。
这里了解的是触发dns的是hashcode方法,也就是在计算键的hashcode决定存储位置的时候触发的dns。但是在HashMap中put时会自动调用hashcode来计算,从而导致在序列化时就触发dns,但是由源码可知
1 |
|
当hashCode为-1时将会重新计算,也就是说我们需要在put之后将hashCode设置回-1。从而让它能够再次进行hashcode计算,触发dns。所以在ysoserial中可以看到payload的这段
1 |
|
跟进过程
上图是HashMap的readObject的最后一段,可以看到通过读取字节数据然后反序列化成对象,再调用putVal方法存储。这里的hash函数的作用是计算这个键的hashcode,然后决定存储到哪个桶当中(桶数组的索引为hashcode),之所以一个桶当中会出现多个元素就是因为发生了hash碰撞,即不同的键对应着相同的hashcode。
然后进入hash方法
可以发现只要键不为空就会调用键自带的hashcode方法,于是我们继续跟进hashcode方法。
然后通过handler这个变量调用URLStreamHandler中计算hashcode的方法。
到这里就可以看到url这个参数了,也就说明我们离触发不远了。
然后这里的getByName就是通过主机名查询IP地址,也就是dns查询,所以这里会触发dns。如果dnslog中出现了记录,那么就说明存在反序列化。