Java反序列化-URLDNS链分析

概述

URLDNS是ysoserial中的一条反序列化链名称,主要作用就是可以指定一个URL,当目标进行反序列化后会发起DNS请求,通过观察DNSlog就能判断序列化数据是否被反序列化。下面通过下载ysoserial源码,对URLDNS进行动态调试,了解其中原理,顺带了解一下ysoserial构造序列化数据的过程。

调试前准备

下载源码:https://github.com/frohoff/ysoserial

  1. 用IDEA打开源码,当项目中包含pom.xml时IDEA会自动根据其中配置下载依赖。
  2. ysoserial的payload源码位于./src/main/java/ysoserial/payloads/中。
  3. 每个payload中都有一个main函数,其中调用了PayloadRunner.run,可以通过执行这些mian函数来单独调试某个payload。
  4. 每个PayloadRunner.run()都会尝试接收一个命令行参数作为要执行的命令,由于这里是URLDNS链,所以参数是传递一个URL。在IDEA中可以进入进入运行配置中指定参数。

image.png
image.png

  1. 接着就可以下断点开始调试了。

image.png
image.png

分析

PayloadRunner.run()

在main函数中下断点后开始一点点跟踪。
函数执行首先进入PayloadRunner.run()
image.png
PayloadRunner.run()中第一行实例化了ExecCheckingSecurityManager类,并调用callWrapped()
callWrapped()接收一个参数匿名内部类,该内部类实现了Callable接口,实现了接口的call()方法。
跟进callWrapped()方法可以看到,里面最终会调用callable.call(),因此我们直接分析call()的实现。
image.png

Callable.call()

call()方法中定义的command尝试接收命令行参数,作为触发反序列化时要执行的命令,当前就是接收我们在开始调试前设置的参数值。
如果没有给定参数时,则默认值为calc.exe。
image.png
image.png
接着就是实例化前面PayloadRunner.run()中传入的URLDNS.class,并调用其中的getObject()方法。
image.png
最后对getObject()方法返回的对象进行序列化并return。
image.png
上图中还有一个Utils.releasePayload(payload, objBefore);,其作用是判断payload是否实现ReleaseableObjectPayload接口,但URLDNS没有实现该接口,因此在此不需要在意。
image.png
最终PayloadRunner.run()第一段代码运行完,就是给serialized变量赋值上序列化数据,然后供后面进行反序列化,触发DNS解析。
image.png

getObject()

上面分析call()时,调用了URLDNS类的getObject()方法,所以也需要分析一下。在PayloadRunner.run()中实际进行序列化的对象就是通过调用getObject()返回的,因此getObject()就是构造序列化数据的重点内容。

URLDNS的序列化数据构造分析

getObject()构造了一个URL类的实例化对象,然后put到了HashMap中,最后返回的是一个HashMap,然后PayloadRunner.run()再对返回的HashMap进行序列化,所以最后反序列化时,调用的是HashMap的readObject()
image.png
要了解最后是如何触发DNS解析的,那就需要在HashMap的readObject()下一个断点进行分析。
在readObject()中需要重点关注的是构造函数private void readObject(ObjectInputStream s)中的putVal(hash(key), key, value, false, false);,因为ysoserial注释中说了是在调用hashCode()中触发了DNS解析。
image.png
hashCode()就在HashMap类的putVal()第一个参数hash(key)中调用的。
image.png
image.png
前面在getObject()中我们可知,是把URL类作为key设置到HashMap中的,因此这里key.hashCode()调用的是URL类的hashCode()
我们继续跟进到URL类的hashCode()中,通过下图可知,实际上URL类的hashCode()是调用了URLStreamHandler类的hashCode()。因为这里的hashCode变量在getObject()中通过反射的方式设置为-1,因此会执行928行的handler.hashCode(this),重新计算hashCode
image.png
我们继续跟进到URLStreamHandler类的hashCode(),可以看到调用了getHostAddress(u)。这⾥ InetAddress.getByName(host)的作⽤是根据主机名,获取其IP地址,在⽹络上其实就是⼀次 DNS查询。
image.png
自此,执行完getHostAddress()后就能收到DNSlog了,对于URLDNS的反序列化执行过程分析结束。

防止序列化过程中进行DNS解析

getObject()中第一部分内容实例化了一个SilentURLStreamHandler类,最终这个实例化的对象会在实例化URL类时作为第三个参数。
image.png
注视中写到:

Avoid DNS resolution during payload creation
Since the field java.net.URL.handler is transient, it will not be part of the serialized payload.

这里的SilentURLStreamHandler类是在URLDNS.java下面定义的,继承了URLStreamHandler类,重写了openConnection()getHostAddress()
image.png
他这里的作用其实注释中写的很明显了,就是防止在序列化过程中调用了getHostAddress(),导致DNS解析,这样就避免了误认为是目标机器对数据进行了反序列化的情况出现。

参考

参考自phith0n师傅的《Java安全漫谈》系列

posted @ 2023-11-02 14:46  Gcker  阅读(164)  评论(0编辑  收藏  举报