URLDNS链分析
Ysoserial初使用
环境搭建
项目地址:https://github.com/frohoff/ysoserial
1 | git clone https://github.com/frohoff/ysoserial.git |
配置maven
然后用maven自动导包进去
运行主函数,编译运行正常
处理了一个报错
当时所有的包都无法导入
https://blog.csdn.net/q5926167/article/details/119318876
终于参考这里的文章解决了
使用方法
使用yo生成反序列化payload:
方法1 IDEA添加参数&写入文件
打印出的payload不能直接用
所以在序列化完之后写入本地文件,这里序列化对象时重写了writeObject方法,就和反序列化思路一样,我们在这个方法的最后添加一段用来输出为本地文件的功能
如此payload保存到1.ser中,以便我们后续使用
方法2 打包成jar
payload测试&传值
我们尝试使用我们刚刚创建的cc来攻击本地server
jdk1.7+tomcat8.5搭建环境
写一个简单的Servlet,doPost中反序列化传来的数据
1 | public class TestServlet extends HttpServlet { |
然后在web.xml中配置路由
1 | <servlet> |
maven中导入存在漏洞的包:
1 | <dependency> |
启动tomcat,部署页面,用curl传值
成功触发漏洞,说明环境正确,ysoserial生成payload正确
前置准备
利用ysoserial生成urldns payload
环境就是测试搭建ysoserial一文中搭建的webserver
修改参数,生成payload,然后传入webserver
可以看到有dnslog记录,复现成功,下面开始调试分析
HashMap类
HashMap的基本用法 https://blog.csdn.net/wxgxgp/article/details/79194360
HashMap的实现原理https://wiki.jikexueyuan.com/project/java-collection/hashmap.html
HashMap的数据结构:
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体
从下图中可以看出,HashMap 底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个 HashMap 的时候,就会初始化一个数组。
调试URLDNS利用链
HashMap.readObject()
通过ysoserial和我们动态调试可以得到:
URLDNS链的起点也是根源在HashMap的readObject方法,我们知道java中,反序列化漏洞的触发入口一般都在重写的readObject方法中,因此我们从这里下手
在servelt的反序列化方法那里打上断点
跟进到readObject方法里面,在readObject0处断点
几步步过之后,来到了HashMap.readObject,这也是URLDNS的入口
下面就是在readObject方法里面动态调试
java.io.ObjectInputStream.defaultReadObject() 方法从该流中读取当前类的非静态和非瞬态字段。这也许只能称为从类反序列化readObject方法。它会抛出NotActiveException如果它被调用。
这里的判断跳过
下面是一系列初始化HashMap的操作,创建空数组,然后计算capacity,这里没有什么特别的地方
开始遍历键值对
HashMap.putForCreate()
这里步入
HashMap.putForCreate()方法
可以看到是计算哈希的方法,我们步入这个hash方法
第一次分析的时候,认为hash方法可能是个没啥用就计算的方法,没有跟进去看,略过了。
所以要细心,不能遗漏
HashMap.hash()
我们dns查询的网址被当作一个URL对象传入
这里hashSeed为0,略过那个if
步入下面的362行的hashCode方法
java.net.URL.hashCode()
hashCode != -1
878行的判断条件hashCode != -1
当我们正常传入反序列化对象时,他会在反序列化读取给key赋值的时候,改变hashCode的值
因此在构造payload的时候要手动让这个条件为true
步入这里的hashCode方法
java.net.URLStreamHandler.hashCode
根据绿字描述,这个方法是根据URL对象的不同协议和需求,生成一个供哈希表索引的一个hashcode返回
根据协议计算hashCode加到h上
hashCode方法就是位运算生成hash值
步入他获取域名的方法
java.net.URLStreamHandler.getHostAddress
第一个if判断跳过,然后getHost方法获取host
跟进getHost方法
步入getByName方法
java.net.InetAddress.getByName(java.lang.String)
继续步入getAllByName方法
java.net.InetAddress.getAllByName()
java.net.InetAddress.getAllByName(java.lang.String)
java.net.InetAddress.getAllByName(java.lang.String, java.net.InetAddress)
进这个重载方法,参数reqAddr为null
第一个if判断,由于host不为空,跳过
第二个判断,我们的host开头不是[,跳过ipv6的判断
第三个判断,根据注释可以了解到如果传入host的是ip地址,就不会进行lookup,也就是dns查询
addr==null时,进到这个if里面后面的都跳过了
一直到最后的getAllByName0(),进行域名解析工作
java.net.InetAddress.getAllByName0(java.lang.String, java.net.InetAddress, boolean)
security返回null
下面是查找有没有查询缓存,如果有的话会直接返回缓存地址数组
这里我们得让addresses为null,才能步入getAddressFromNameService方法
跟进getAddressFromNameService方法
java.net.InetAddress.getAddressesFromNameService
一开始初始一些变量的值
判断host是否在table里面
然后跟进到下面的遍历
跟进lookupAllHostAddr方法
其实到这一步,已经完成了一次dns查询,这条链子也就到头了
分析URLDNS构造链
调试结束,我们如何构造呢
这里handler调用hashCode
handler值我们可以在URL对象的构造函数里给
URLStreamHandler是个抽象类,无法直接实例化,这里yeoserial找到了另一个可以使用的类
SilentURLStreamHandler
Java中子类会覆盖父类的同名方法,所以我们在子类里重新写了这两个方法,可以避免在ysoserial生成payload的时候发起dns解析造成干扰结果
这样我们可以拿到一个URLStreamHandler实例对象
让hashCode = -1
上面遗留下来的问题,在java.net.URL.hashCode()中,如果hasnCode不等于-1,链子的下半部分就无法进行
ysoserial中用反射让hashCode强制赋值为-1
总结
左边构造的payload可以让我们顺着右边的方法层层取出URLStreamHandler类来调用其getHostAddress方法
漏洞最核心的地方在右边调用链的最最下面,我们上面的所有工作都是为了让其能通过反序列化的readObject方法自动执行链条,这一个上一章讲过
所以要一直找利用类,来满足我们:
调用私有方法
让链条可以顺利在我们想要的路上进行,不会被条件搞乱
从最外层开始到内部的核心数据不改变
等等等等(因为还是初学所以暂时总结,马上去找大师傅的心法
所以首先要调试链子,然后再去分析,分析是建立在调试的基础上的,以后自主审计是另一回事,但是学习过程中先要调试。
分析的时候看着前人挖出来的链子才能知道他为什么找这些类。