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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
TC_OBJECT - 0x73
TC_CLASSDESC - 0x72
className
Length - 18 - 0x00 12
Value - org.example.Person - 0x6f72672e6578616d706c652e506572736f6e
serialVersionUID - 0xcc 5e 4d 5d ef ba cd 05
newHandle 0x00 7e 00 00
classDescFlags - 0x02 - SC_SERIALIZABLE
fieldCount - 3 - 0x00 03
Fields
0:
Int - I - 0x49
fieldName
Length - 3 - 0x00 03
Value - age - 0x616765
1:
Object - L - 0x4c
fieldName
Length - 6 - 0x00 06
Value - idCard - 0x696443617264
className1
TC_STRING - 0x74
newHandle 0x00 7e 00 01
Length - 18 - 0x00 12
Value - Ljava/lang/String; - 0x4c6a6176612f6c616e672f537472696e673b
2:
Object - L - 0x4c
fieldName
Length - 4 - 0x00 04
Value - name - 0x6e616d65
className1
TC_REFERENCE - 0x71
Handle - 8257537 - 0x00 7e 00 01
classAnnotations
TC_ENDBLOCKDATA - 0x78
superClassDesc
TC_NULL - 0x70
newHandle 0x00 7e 00 02
classdata
org.example.Person
values
age
(int)0 - 0x00 00 00 00
idCard
(object)
TC_NULL - 0x70
name
(object)
TC_NULL - 0x70

image.png
具体的过程就是这样,如果想要了解更多细节,可以自行去跟进一下。

URLDNS

HashMap前置

学了这么久的前置知识总算可以开始正式跟进链子了,接下来的过程就和php中的反序列化差不多了,只要对逻辑填空即可。虽然并不需要过深的了解hashmap得实现原理,但是也大概的看一下有个底吧。
image.png

这里了解的是触发dns的是hashcode方法,也就是在计算键的hashcode决定存储位置的时候触发的dns。但是在HashMap中put时会自动调用hashcode来计算,从而导致在序列化时就触发dns,但是由源码可知

1
2
3
4
5
6
7
public synchronized int hashCode() {  
if (hashCode != -1)
return hashCode;

hashCode = handler.hashCode(this);
return hashCode;
}

当hashCode为-1时将会重新计算,也就是说我们需要在put之后将hashCode设置回-1。从而让它能够再次进行hashcode计算,触发dns。所以在ysoserial中可以看到payload的这段

1
2
3
4
5
6
7
8
//使用内部方法  
f.setAccessible(true);

// put 一个值的时候就不会去查询 DNS,避免和刚刚混淆
f.set(url, 0xdeadbeef);
hashMap.put(url, "zeo");

// hashCode 这个属性放进去后设回 -1, 这样在反序列化时就会重新计算 hashCodef.set(url, -1);

跟进过程

image.png
上图是HashMap的readObject的最后一段,可以看到通过读取字节数据然后反序列化成对象,再调用putVal方法存储。这里的hash函数的作用是计算这个键的hashcode,然后决定存储到哪个桶当中(桶数组的索引为hashcode),之所以一个桶当中会出现多个元素就是因为发生了hash碰撞,即不同的键对应着相同的hashcode。
然后进入hash方法
image.png
可以发现只要键不为空就会调用键自带的hashcode方法,于是我们继续跟进hashcode方法。
image.png
然后通过handler这个变量调用URLStreamHandler中计算hashcode的方法。
image.png
到这里就可以看到url这个参数了,也就说明我们离触发不远了。
image.png
然后这里的getByName就是通过主机名查询IP地址,也就是dns查询,所以这里会触发dns。如果dnslog中出现了记录,那么就说明存在反序列化。


Java反序列化学习_URLDNS
http://example.com/2024/04/02/Java反序列化学习_URLDNS/
Author
ghh
Posted on
April 2, 2024
Licensed under