https://sec.lz520520.com/2021/07/612/
前言
JAVA JDK原本就提供了丰富的集合操作,但是在某些场合下,可能无法满足,因此Apache Commons-collections组件提供了更加丰富的集数据结构。 原本是一个扩展了Java标准库里的Collection结构的第三方基础库,受到广大的开发者的欢迎与使用,但是由于该第三方库存在安全问题,因此大部分人对其进行了研究。现在大部分受漏洞影响版本为3.2.1,截止2021\9\24 common-collections已更新至4:4.4版本
ysoserial是对java反序列化链构造payload的一个集大成工具,主要结构如下
https://www.cnblogs.com/tr1ple/p/12437310.html
URLDNS作为ysoserial反序列化利用工具里最简单的一个模块,我们可以拿来来入门练练手,跟踪一下它是怎么是通过反序列化来执行一个URLDNS的探测的。
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS “http://j1lfhp.dnslog.cn“ >X.bin
直接从网上抄一段代码来对X.bin文件进行一个反序列化(即我们认为的漏洞需要存在一个readObject方法,而大部分产生漏洞的原因是对其readObject方法进行了重写,导致的命令执行)
import java.io.*;
public class main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream("X.bin");
ObjectInputStream bit = new ObjectInputStream(fis);
bit.readObject();
}
}
开心的看到发来了数据
writeObject 序列化
readObject 反序列化
环境搭建
直接去Github将ysoserial的源码(而不是release)直接放入idea中
更新Pom.xml后,发现某些依赖依旧会爆红,但是不影响整个工具的使用
搜索
简单分析
Gadget Chain:
HashMap.readObject()
HashMap.putVal()
HashMap.hash()
URL.hashCode()
序列化
shift+ctrl+F 搜索URLDNS找到类,里面有两个方法和一个内部类,要观察的方法为getObject
我们在调试分析前要注意先添加一个参数,不然在main方法debug运行程序时会返回空白,要注意的是这里是URLDNS类,实例化的对象会自动先序列化 再反序列化
代码提示了利用链,因此记得在hashmap下的readObject方法也下个断点,如下图所示
我们getObject开始跟进,在进入此方法前,url=http://xxxx已经从外部获取到
第53行暂时忽略,简单说一下这个类的意思
URLStreamHandler,引用别人对这个类的理解。
一般而言, URL 的格式是: protocol://[authority]hostname:port/resource?queryString 。 URL 类能够解析出 protocol、 hostname 、 port 等信息。 Protocol 决定了交互规范,通用的协议,比如 HTTP 、 File 、 FTP 等协议, JDK 自带了默认的通讯实现。当然,自定义实现是允许的。 Hostname 和 port 一般用于 Socket 或者基于 Socket 其他协议通讯方式。Resource 即资源上下文。可能读者利用 URL ,通过指定协议( protocol )来获取指定资源的读写,比如 JDK 内置了HTTP 、 File 、 FTP 等协议的处理方法。
第55行创建了一个Hashmap 对象名为ht,然后put了一个键值对,u=http://xxx.com,这里的u为URL类代码的实例化对象
因此hashmap的key就为u对象,value值为http:////xxxx.com
到了第58行,可以看到传入了三个参数
Reflections.setFieldValue(u, “hashCode”, -1); 跟进此方法
然后通过调用下面的getField方法中反射机制的getDeclaredField来获取hashcode方法的成员变量(fieldName),之后又使用getSuperclass方法获取了hashcode
然后return
可以看到 return 了hashcode的成员变量
接着再次回到了setFieldValue,使用field.set(obj, value); 将hashcode,url,-1进行关联。
将url(obj)这个类实例对象的成员变量hashcode值修改为-1
此时就到了return ht 返回hashmap的对象,使用Serializer.serialize对其进行进行了序列化操作
跟进serializ方法,最终可以看到对传入的obj对象进行了序列化操作
利用反序列化
后续不断单步调试后,当URLDNS模块自动进行反序列化操作时,到达了PayloadRunner类执行了反序列化操作
跟进,直到putVal方法
putVal方法中会调用hash方法,其中,hash方法的参数是一个java.net.URL对象,跟入hash方法
hash方法这里判断了key是否为空,不为空就调用了key的hashCode方法并计算出值,key是一个java.net.URL对象(key的值实际上就是域名),然后我们F7继续进入到URL类的hashCode方法
可以看到这里判断hashcode的值是否为-1,如果不为-1的化,就return hashcode ,否则就用handler(URLStreamHandler的对象)的hashcode方法
之后就获取了url的协议名称,url的地址(这里的地址是指从url到ip,在网络上也就是DNS请求)
如下图所示,getByName
到此,URLDNS结束
问题
1、为啥要对SilentURLStreamHandler(继承于URL类的URLStreamHandler)实例化一个新的URLStreamHandler,原生的URLStreamHandler不可以吗?
解答:
原因是SilentURLStreamHandler类对其getHostAddress进行了置空操作,为了让HashMap在第一次put元素时,不执行DNSLOG请求,因此,ysoserial重写了getHostAddress方法,将该方法置为空实现,这样更能保证效果。
2、为啥一开始要设置URL的成员变量hashCode为-1?
原因是在URL对象创建时在最初hashcode值就位-1,而在getObject方法的57行使用了hashmap.put,此时代码会重新去计算hashcode的值,就不再等于-1了,因此根据代码hashcode=handler.hashcode,我们需要通过反射来对hashcode进行赋值为-1,否则就不会执行后续的代码(反序列化)
如图所示,hashcode值发生变化
总结
利用链
1、Deserializer.deserialize
2、hashmap.readObject
3、HashMap.putVal()
4、hashmap.hash()
5、URL.hashcode()
6、URLStreamHandler.hashCode().getHostAddress
7、URLStreamHandler.hashCode().getHostAddress.InetAddress.getByName
最简单的来说就是URLDNS模块将getHostAddress这个方法做成对象,在反序列化时又被从新调用回来