0x00、前言
延伸java序列化和java反射的内容,针对一些gadget利用链的一些学习,理解一下利用链的分析过程。
本文学习URLDNS反序列化链,也是java反序列化利用链里面最简单的一条,也利用java反序列化和反射的相关知识,可把前两篇java基础学习的知识运用在利用链里面,记录下利用链相关知识点。
本文角度两个方面,一是从ysoserial工具利用URLDNS角度分析学习,另一个角度是从URLDNS利用链分析学习。
0x01、URLDNS链简述
URLDNS是JAVA复杂的反序列化链中最简单的一条,它不是一条真正意义上的“利⽤链”。因为它所能产生的结果不是命令执⾏,⽽是⼀次DNS请求。
URLDNS通常用于快速监测是否存在反序列化漏洞,尤其对无回显的漏洞检测,原因:
- 只依赖原生类
- 不限制jdk版本
也就是说URLDN可直接调用java内置库即可进行操作,无需依赖其他第三方组件,同时不限制jdk版本不通带来的语言代码差异。
URLDNS链主要问题产生于HashMap,HashMap重写了readObject()反序列化方法,并且参数可控导致序列化漏洞。
HashMap简单介绍:
HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
HashMap 是无序的,即不会记录插入的顺序。
HashMap 继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。
0x02、yso-URLDNS利用链分析
ysoserial环境准备
运行环境:idea
java环境: jdk8
漏洞环境:ysoserial(https://github.com/frohoff/ysoserial)
idea相关配置:
下载ysoserial漏洞环境后在idea打开项目,idea会自动同步pom.xml中的依赖,注意的点是,需要更改文件-项目结构-项目设置-项目中的sdk版本和sdk默认值,如下图即可,不然在编译上可能会报错。随后构建项目即可。
通过pol.xml知道主程序位置
在运行或者debug调试GeneratePayload.java文件,出现下面红色提示表示运行正常,项目部署成功,便可进行urldns链测试。
运行-编辑配置里输入测试参数
相当于使用主程序直接执行命令
yso-URLDNS链分析
yso生成URLDNS利用的序列化数据,主要为URLDNS
、url
两个参数
在主程序获取参数下断点
进行debug调试,参数args[0]
即URLDNS传递给payloadType
变量,参数args[1]
即url传递给command
变量
获取Class类对象,类为GeneratePayload.class.getPackage().getName() + ".payloads." + className
对应得就是ysoserial.payloads.URLDNS
类
对应返回给payloadClass
对象
接下来对Class对象
进行实例化
跟进getObject()
方法,传递command参数也就是传入的url参数
public Object getObject(final String url) throws Exception { |
getObject方法中建立了URLStreamHandler
流对象和HashMap
对象,其中handler对象为URLStreamHandler
的子类SilentURLStreamHandler
URLStreamHandler handler = new SilentURLStreamHandler();
这次调用的子类SilentURLStreamHandler
方法,去规避生成序列化的过程中触发dns,因为调用子类在获取getHostAddress
方法时返回Null
,并不执行父类URLStreamHandler
中的getHostAddress()
方法
static class SilentURLStreamHandler extends URLStreamHandler { |
生成URL对象
,并将url参数
和URLStreamHandler
传入URL对象
中
随后执行HashMap
的put()
方法,将URL对象
作为HashMap值
,将url参数值
作为key的值
存储在HashMap
中ht.put(u, url);
再通过反射机制将URL对象
的hashCode值
设置为-1Reflections.setFieldValue(u, "hashCode", -1);
最后返回HashMap对象ht
在获取HashMap对象后对其进行序列化操作,这边没有设置out定向输出的文件,out就没对应数值,对应命令行最后加 > serialize.ser
。
由于HashMap
对序列化writeObject()
也进行了重写,所以会调用HashMap
的序列化方法进行序列化操作,正常的序列化操作。
遍历HashMap
中key
和value
进行序列化写入
到此ysoserial工具的URLDNS利用链就执行完成,输出payload序列化的数据。
yso-URLDNS链反序列化
通过对输出的序列化数据,进行反序列化,触发漏洞。
payload
package ysoserial; |
0x03、URLDNS链反序列化分析
正常反序列化readObject阶段
这里就跟着payload的反序列化进行分析下去吧,(当然也可以直接在HashMap重写的readObject方法进行下断点,可以直接分析反序列化触发的漏洞),这里从头来可以理解一下运行流程,但前些正常的反序列化过程比较长有些就略过了,记录下关键的步入点吧。
反序列化处下断点,debug运行
运行后,需要强行步入(Alt+Shift+F7)readObject方法,注:步入会直接跳过
进入后,还需要再次强行步入进入readObject方法
进入后,前面都是正常的一些判断,步过直到调用readObject0()
方法
进入readObject0()方法,然后又是一系列的正常操作和判断,再关注到TC的判断如下图,由于TC为Obeject对象,并非String类对象,所以判断为false,进入调用readOrdinaryObject方法(读取二进制数据)
进入readOrdinaryObject方法,这里读取序列化数据,并将序列化数据赋值给对象
后面将对象进行实例化并进行一些判断操作,一直到进入readSerialData
方法
readSerialData
方法对序列化数据进行读取,一直到调用invokeReadObject
方法
通过反射invoke去判断对象是否有重写readObject方法
返回ma.invoke
,跟进查看ma.invoke
返回delegate.invoke
,跟进delegate.invoke
方法
获得返回HashMap
重写的readObject
方法
跟进返回的invoke0
,便进入调用的HashMap
重写的readObject
方法
到这里,进入HashMap
重写的readObject()
方法
HashMap反序列化readObject阶段
遍历HashMap
中key
和value
的值,并反序列化读取还原key
和value
的值,随后进行hash()
运算保证唯一
跟进hash()
,hash方法判断key是否为空,不为空就调用URL
类中的hashcode()
方法,key值为url如下图标识所示
跟进当前hashcode()
方法,会首先判断hashcode是否为-1,为-1则进入URLStreamHandler流对象的hashcode()
方法
流对象的hashcode()
调用了getHostAddress()
方法
跟进getHostAddress()
方法,返回URL类的u.getHostAddress()
方法
跟进u.getHostAddress()
方法,调用getByName()
从而解析请求dnslog,导致漏洞触发。
到此URLDNS链的反序列化触发访问dnslog就完成。
总结URLDNS反序列化链流程
ObjectInputStream
读取HashMap
的序列化文件- 通过正常
Object
流readObject
反序列化文件获取二进制数据 - 对二进制序列化数据进行读取并建立实例对象
- 通过对实例对象判断是否存在重写方法
- 获取到
HashMap
对象重写反序列化readObject
方法,跳转执行该对象的readObject
方法。 - 遍历序列化
Key
和value
值,进行hash
运算 hash
方法中key
不为空时,调用URL
类的hashcode
方法- 当
hashcode
等于-1时,调用URLStreamHandler
流的hashcode
方法 hashcode
方法中调用了getHostAddress()
方法getHostAddress()
方法返回URL
类的u.getHostAddress()
,URL
类的getHostAddress()
方法调用getByName()
方法从而解析请求dns
其中步骤1-5为正常反序列化的读取步骤,6-11为获取HashMap对象执行重写readObject方法步骤。
0x04、POC编写
通过上述过程可以理出来代码重要步骤。
主要生成HashMap对象和URL对象
HashMap hm=new HashMap(); |
并通过反射控制URL类中hashcode的值
Field code = Class.forName("java.net.URL").getDeclaredField("hashCode"); //通过反射去控制hashCode值 |
放入hashMap put值
hm.put(u,123); //设置HashMap键值对 |
通过设置hashcode为-1去触发dns请求
code.set(u,-1); //通过设置hashcode为-1去触发dns请求 |
然后就是序列化HashMap对象,最后反序列化。
完整POC:
package ysoserial; |
0x05、参考链接
https://mp.weixin.qq.com/s/MiBpBHRUkJbEwTcERgEx5w
https://paper.seebug.org/1242/#commons-collections