Java反序列化-URLDNS链分析
概述
URLDNS是ysoserial中的一条反序列化链名称,主要作用就是可以指定一个URL,当目标进行反序列化后会发起DNS请求,通过观察DNSlog就能判断序列化数据是否被反序列化。下面通过下载ysoserial源码,对URLDNS进行动态调试,了解其中原理,顺带了解一下ysoserial构造序列化数据的过程。
调试前准备
下载源码:https://github.com/frohoff/ysoserial
- 用IDEA打开源码,当项目中包含pom.xml时IDEA会自动根据其中配置下载依赖。
- ysoserial的payload源码位于./src/main/java/ysoserial/payloads/中。
- 每个payload中都有一个main函数,其中调用了
PayloadRunner.run
,可以通过执行这些mian函数来单独调试某个payload。 - 每个PayloadRunner.run()都会尝试接收一个命令行参数作为要执行的命令,由于这里是URLDNS链,所以参数是传递一个URL。在IDEA中可以进入进入运行配置中指定参数。
- 接着就可以下断点开始调试了。
分析
PayloadRunner.run()
在main函数中下断点后开始一点点跟踪。
函数执行首先进入PayloadRunner.run()
PayloadRunner.run()
中第一行实例化了ExecCheckingSecurityManager
类,并调用callWrapped()
callWrapped()
接收一个参数匿名内部类,该内部类实现了Callable
接口,实现了接口的call()
方法。
跟进callWrapped()
方法可以看到,里面最终会调用callable.call()
,因此我们直接分析call()的实现。
Callable.call()
call()
方法中定义的command尝试接收命令行参数,作为触发反序列化时要执行的命令,当前就是接收我们在开始调试前设置的参数值。
如果没有给定参数时,则默认值为calc.exe。
接着就是实例化前面PayloadRunner.run()
中传入的URLDNS.class,并调用其中的getObject()方法。
最后对getObject()
方法返回的对象进行序列化并return。
上图中还有一个Utils.releasePayload(payload, objBefore);
,其作用是判断payload是否实现ReleaseableObjectPayload接口,但URLDNS没有实现该接口,因此在此不需要在意。
最终PayloadRunner.run()
第一段代码运行完,就是给serialized变量赋值上序列化数据,然后供后面进行反序列化,触发DNS解析。
getObject()
上面分析call()
时,调用了URLDNS类的getObject()
方法,所以也需要分析一下。在PayloadRunner.run()
中实际进行序列化的对象就是通过调用getObject()
返回的,因此getObject()
就是构造序列化数据的重点内容。
URLDNS的序列化数据构造分析
getObject()
构造了一个URL类的实例化对象,然后put到了HashMap中,最后返回的是一个HashMap
,然后PayloadRunner.run()
再对返回的HashMap进行序列化,所以最后反序列化时,调用的是HashMap的readObject()
。
要了解最后是如何触发DNS解析的,那就需要在HashMap的readObject()
下一个断点进行分析。
在readObject()中需要重点关注的是构造函数private void readObject(ObjectInputStream s)
中的putVal(hash(key), key, value, false, false);
,因为ysoserial注释中说了是在调用hashCode()
中触发了DNS解析。
而hashCode()
就在HashMap类的putVal()
第一个参数hash(key)
中调用的。
前面在getObject()
中我们可知,是把URL类作为key设置到HashMap中的,因此这里key.hashCode()
调用的是URL类的hashCode()
。
我们继续跟进到URL类的hashCode()
中,通过下图可知,实际上URL类的hashCode()
是调用了URLStreamHandler
类的hashCode()
。因为这里的hashCode
变量在getObject()
中通过反射的方式设置为-1,因此会执行928行的handler.hashCode(this)
,重新计算hashCode
。
我们继续跟进到URLStreamHandler
类的hashCode()
,可以看到调用了getHostAddress(u)
。这⾥ InetAddress.getByName(host)
的作⽤是根据主机名,获取其IP地址,在⽹络上其实就是⼀次 DNS查询。
自此,执行完getHostAddress()
后就能收到DNSlog了,对于URLDNS的反序列化执行过程分析结束。
防止序列化过程中进行DNS解析
getObject()
中第一部分内容实例化了一个SilentURLStreamHandler
类,最终这个实例化的对象会在实例化URL类时作为第三个参数。
注视中写到:
Avoid DNS resolution during payload creation
Since the fieldjava.net.URL.handler
is transient, it will not be part of the serialized payload.
这里的SilentURLStreamHandler
类是在URLDNS.java下面定义的,继承了URLStreamHandler
类,重写了openConnection()
和getHostAddress()
。
他这里的作用其实注释中写的很明显了,就是防止在序列化过程中调用了getHostAddress(),导致DNS解析,这样就避免了误认为是目标机器对数据进行了反序列化的情况出现。
参考
参考自phith0n师傅的《Java安全漫谈》系列