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的子类SilentURLStreamHandlerURLStreamHandler 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