Java Deserialization - Chapter Ⅱ

在上一节中我们大致讲解了Java中反序列化的基础知识以及造成漏洞的原因,这一节中我们主要来看一下URLDNSHashMap在反序列化中的利用

URLDNS

首先在java内置库中并没有叫做URLDNS的类,这个叫法主要来源于一个java反序列化利用工具ysoserial中的一个利用链的名称,其主要指的是java内置库中java.net.url

根据官方文档介绍java.net.url类主要是用来访问URL网络资源,获取url对象等相关的信息

此类实现了Serializable接口

Clip_20230410_152606

其中我们需要关注的只有两个方法,那就是equalshashcode

equals

equals方法用于比较两个URL对象是否相等,比较依据为URL对象具有相同的协议,是否是相同的端口,以及是否具有相同的文件和片段

除此之外如果两个主机名都可以解析成相同的IP地址,则两台主机被认为是等效的,在这一过程中equals会对传入的url进行DNS解析

下面来看一下equals的比较流程

Clip_20230410_153307

首先在equals中判断了传入对象是否是URL的子类,如果符合条件则调用this.handler.equals

其中this.handler是一个URLStreamHandler

Clip_20230410_153541

Clip_20230410_153648

URLStreamHandler:equals中简单判断字符串是否相同后调用sameFile

Clip_20230410_153942

sameFile中对两个URL对象的文件,端口和协议进行比较,如相同则调用hostsEqual

Clip_20230410_154437

Clip_20230410_154857

hostsEqual中调用getHostAddress完成对主机名的解析

hashcode

hashcode顾名思义就是计算hash表的索引值

相比于equalshashcode的流程要简单的多

Clip_20230410_155800

同样也是调用URLStreamHandler中的方法

但这里需要注意的是hashcode会先判断当前hashcode的值,如果不是-1,会立即返回当前缓存的哈希值

Clip_20230410_160124

URLStreamHandlerhashcode方法中同样也是调用getHostAddress解析主机名

HashMap

ok,上面我们了解urlequalshashcode两个方法的一个特性,无论是使用equals还是hashcode都会触发DNS的解析

我们可以称它为一个sink,但是sink还是不够的,我们还需要找到一个可以触发sink的路径,这也就是我们常说的gadget也就是利用链

通过上一节的基础知道我们知道,要想实现反序列化就需要实现Serializable或者Externalizable并重写readObject方法

而在URLDNS最常用的一个gadget就是java.util.HashMap,做过java开发的应该对该类非常熟悉了

下面来看一下HashMap的具体实现

Clip_20230410_163417

首先HashMap实现了Serializable接口并重写了readObject方法

readObject

Clip_20230410_164106

除去前面一些初始化的代码,主要来看框内的,利用for循环遍历序列化对象的keyvalue值并调用putVal装填到map

putVal需要五个参数分别是经过hash方法计算的hashcode,key,value以及两个布尔型的参数

主要来看一下hash方法

Clip_20230410_164955

hash中首先调用传入对象的hashCode方法将返回值和返回值移位16的值做异或操作,将最后的值作为哈希值

到这里我们就可以和前面的URL:hashcode衔接起来了

如果我们在序列化HashMap时将key设置为一个URL对象,那么在使用HashMap进行反序列化时就会调用URL对象的hashCode方法触发DNS解析

构造

在构造之前我们需要解决两个问题,看下图

image-20230410202149426

我们在向hashmapput时必定要会触发一次DNS解析,这显然不是我们希望发生的

第二就是hashcode问题

image-20230410202443306

当我们在序列化如果按照正常put数据当到达URL对象hashcode由于会触发DNS解析,会缓存hashcode

当我们再次反序列化时hashcode不为-1也就无法触发DNS解析

为解决以上问题,我们可以使用反射方式向hashmap中填充数据,并将hashcode初始化为-1

image-20230410203200447

首先声明一个HashMap类型为URL对象和一个整型

随后声明一个URL对象并将我们的payload作为构造参数,接着通过Class.forName拿到我们的HashMap类对象并获取所有方法名

当方法名为putVal时向通过invoke反射调用putValhashmap里填充构造的数据,指定hashcode-1,url对象,键值。剩余两个参数可以从put方法照抄过来

当然除上述方法,你还可尝试使用其他的方法

最后我们通过readObject看一下调用过程

image-20230410204811069

首先ObjectInputStream通过正常反射调用到了HashMapreadObject(具体流程见上一章内容)

随后进入到hash方法中,hash方法则会继续调用URL对象的hashcode

image-20230410210209131

这样就接上了我们最开始URLhashcode流程,触发DNS解析

image-20230410205557322

由于此链并不能达到执行命令的效果,仅仅可以触发DNS解析,所有此链也常作为检测是否存在反序列化漏洞的测试gadget

That’s All