前言
URLDNS链是ysoserial里面的一条简单的利用链,但URLDNS的利用效果是只能触发一次DNS请求,而不能去执行命令,比较适用于漏洞验证这一块。而且URLDNS这条利用链并不依赖于第三方的类,而是JDK中内置的一些类和方法。
反序列化漏洞成因
序列化指把Java对象转换为字节序列的过程,反序列化就是打开字节流并重构对象,那如果即将被反序列化的数据是特殊构造的,就可以产生非预期的对象,从而导致任意代码执行。
Java中间件通常通过网络接收客户端发送的序列化数据,而在服务端对序列化数据进行反序列化时,会调用被序列化对象的readObject( )方法。而在Java中如果重写了某个类的方法,就会优先调用经过修改后的方法。如果某个对象重写了readObject( )方法,且在方法中能够执行任意代码,那服务端在进行反序列时,也会执行相应代码。如果反序列化的数据是可控的情况下,那么我们就可以从某个输入点,输入恶意代码,再去查找在哪个点,我们的输入会被一层一层的带去到我们的触发点去,而这一步叫做寻找利用链的步骤。
动态调试ysoserial
ysoserial jar : https://jitpack.io/com/github/frohoff/ysoserial/master-30099844c6-1/ysoserial-master-30099844c6-1.jar
ysoserial 源码:https://github.com/frohoff/ysoserial
下载源码,导入到IDEA中,刷新maven,下载好依赖,查看pom.xml,搜索mainClass可以找到入口类
进入到GeneratePayload
,配置启动参数
再次运行就可以了
下面分析下ysoserial是怎么生成序列化数据的
随便下个断点进入Utils.getPayloadClass
方法中,代码就简单利用反射获取到了URLDNS
的Class对象
往下走就进入getObject()
方法中
getObject()
方法中,创建了一个hashmap
,并将URL对象当做map的key值,value值随意,最后还通过反射修改了hashCode
的值为-1,这里修改的原因后面分析。
继续往下,就是调用serialize()
方法
进入,可以看到这里就是将上一步返回的HashMap进行序列化输出至控制台
URLDNS链分析
打开ysoserial\payloads\URLDNS.java
,在源码的注释中可以看到对调用链的描述,翻译过来就是
Java URL 类在其 equals 和 hashCode 方法上有一个有趣的属性。作为副作用,URL 类将在比较期间进行 DNS 查找(equals 或 hashCode)。作为反序列化的一部分,HashMap 在它反序列化的每个键上调用 hashCode,因此使用 Java URL 对象作为序列化键可以触发 DNS 查找
调用链如下
1 | * Gadget Chain: |
具体的调用过程,我们下断点调试看看,先生成序列化数据
简单写个反序列化入口
触发DNS查询
下面开始分析,根据上述的Gadget Chain,可见触发点是在HashMap.readObject()
,来到hashmap
的readobject()
方法,然后一直F8,根据Gadget Chain发现使用了putVal()
方法,但这不是重点,重点是会调用hash方法
这里使用了hash方法对key的值进行了处理,我们来跟踪一下hash这个方法看看他具体的实现
如果key不是null就会调用key.hashCode()
方法,跟进hashCode()
方法,这里调用的是URL类中的hashCode()
方法
当hashCode
值不为-1时就直接return,就不会触发hashCode()
方法,也就不会触发接下来的DNS解析,这里hashCode
值默认为 -1,所以会执行 handler.hashCode(this)
,URLDNS链中也通过反射将hashCode
的值设置为-1,也就是URLDNS的getObject
()
方法中设置hashCode
为-1的原因了。
看一下handler
,是URLStreamHandler
类(也是我们传入的handler
),就是上面URLStreamHandler
对象
也就是说这里调用的是URLStreamHandler.hashCode()
方法,跟进hashCode()
方法,发现这里调用了getHostAddress()
方法,见名思意就知道这里是做DNS查询
跟进getHostAddress()
方法,发现会调用getHost()
方法发起DNS请求
到此就结束了,调用链如下
思考
分析过程中,发现HashMap.put()
方法中也调用了hash()
方法,然后去进行hashCode计算等
那么就是说,在put操作的时候,也会触发对应的DNS解析,编写测试代码
成功解析DNS
但是,ysoserial在生成序列化数据的时候却并没有收到DNS解析,原因就在于继承抽象类URLStreamHandler
的SilentURLStreamHandler
类中,重写了openConnection()
和getHostAddress()
因此在调用 put 方法的时候不会触发DNS 查询,下面编写测试代码
1 | public class UrlDNStest2 { |
根据之前分析之所以会产生后面的DNS解析的一个关键是URL.hashCode
的值是-1,那么要想让put()
方法不产生DNS解析,可以在put方法之前设置hashCode
为一个不为-1的值
那为什么反序列化之后又可以进行DNS解析呢,这里查看URL类的源码,可以看到handler
属性被设置为了transient
,在反射的学习中可以知道,被设置了transient
的是无法被序列化的,所以序列化的时候没有DNS解析。
总结
整个调用链梳理下就是
1 | HashMap.readObject() -> HashMap.putVal() -> HashMap.hash() -> URL.hashCode() -> URLStreamHandler.hashCode().getHostAddress() -> URLStreamHandler.getHostAddress().InetAddress.getByName() |
可能存在反序列化漏洞的形式。
1 | 1.入口类的readObject直接调用危险方法。 |