反序列化作为新生代比较亮点的漏洞,利用难度和理解难度相对来说在sql注入,越权这种常见漏洞相比还是比较困难的。网上文章呢对小白可能不是很好理解,今天这篇文章本人就尽量使用浅显易懂的方式给大家讲解一下。
========================================================================================================
首先反序列化是什么?
java反序列化是将序列化的对象转换回原始对象的一个过程。在Java中,序列化是指将对象转换为字节序列以便在网络上传输或保存到文件中。
反序列化则是将字节序列转换回对象的过程。通过反序列化,可以将之前序列化的对象重新恢复成原始的对象,实现对象的持久化和传输。
注意序列化后的对象时可以被用户随意处理的,包括网络传输或保存为文件,这也就奠定了反序列化漏洞的利用条件宽泛与否。
那知道了序列化与反序列化的定义我们来看反序列化漏洞的前提是什么?
反序列化漏洞的产生一定要对方项目存在有高危风险的类库。(比如常见的cc库)
并且在代码中一定是调用了readObject方法尝试还原对象。
如果不存在这两点,基本就不会存在反序列化漏洞。
URLDNS
从urldns开始讲起,在反序列化中urldns是一条完美的适合用于快速检测的链,虽然他只能出发dns请求,但是其不需要依赖第三方类库的特性使其有超高的遍布性。
我们直接看yso中urldns的payload
反序列化漏洞中大家只需要记得最后return的是什么,入口点就是什么。所以在这段payload中入口点就是ht。
那么看ht他new了一个hashmap,这就说明他的入口和hashmap有很大的关联,所以我们直接跟进到hashmap中
进到hashmap还记得上面我说的吗,反序列化漏洞的存在必须其调用过readObject还原对象。所以我们通篇查询是否存在readObject。
果然是存在的,那么这时候我们随便下个断点把,看看他具体在readObject都干了什么。
通过跟进代码我们发现他获取了两个参数key和value,并且在最后一行putval中,使用hash计算了key的哈希值。
直接跟进hashmap的hash进行查看。
可以发现其传入了一个object类型参数key,在key为空时返回0,不为空时调用key下的hashcode进行处理。
我们有key参数,所以继续跟进
这时我们跟进到key的hashcode方法中,他存在判断,当hashcode不等于-1时直接返回hashcode,等于-1时调用handler的hashcode进行处理。所以我们还需要跟进。
跟到这一步,为何会触发dnslog就已经清晰了,在最终handler的hashcode中执行了gethostaddress操作,仔细看他直接收一个参数u。
ok那到这里暂时逻辑清楚了,我们需要制作一个payload满足key不为空,并且hashcode必须是-1,这样就能最终进入到handler的hashcode中执行了gethostaddress。那回过头来看yso的payload
逐行解释
URLStreamHandler handler = new SilentURLStreamHandler();用于处理url的打开,链接,读取等行为
`HashMap ht = new HashMap();`` ``URL u = new URL((URL)null, url, handler);`` ``创建一个url对象u,并传入string类型的url作为我们的dnslog地址`
`ht.put(u, url);``发送请求`
`Reflections.setFieldValue(u, "hashCode", -1);`` ``精髓就在这一句,通过反射修改u对象的hashcode参数为-1`
整理下思路入口点hashmap的readobject,其中在putval时调用hash方法处理了key。在hash方法中key不为空情况下调用了key的hashcode,key的hashcode要求必须为-1才可进行到执行dnslog的逻辑。
所以看ht.put(u, url);这一句,其中u就是key相当于我们传入的dnslog地址,url就是value。url这个可以随意更改,在逻辑中我们也看到了这条链触发和value没有任何关系。
在获得key之后我们达成了key不为空条件,此时通过反射调用hashcode方法,并且修改为-1达成必须为-1的条件,这样完美触发dnslog请求。
cc5
cc链作为经典之一必需拿出来溜溜,再说cc之前需要先知道一些基础知识。
cc中的存在了一个叫TransformMap的类,这个类实现了一系列的转化,在转化过程中值我们可以自行操作,所以为反序列化漏洞提供了无限的可能。
那么知道了基础知识后,我们来看下cc5的payload
以命令执行为例子,反序列化漏洞需要我们生成一个可以命令执行的payload,那么在java中我们知道最常见的命令执行代码就是runtime.exec了。那么就来看一眼yso的payload是怎么构造出命令执行的呢?
Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer("exec", new Class[]{String.class}, execArgs), new ConstantTransformer(1)};
这一行是yso的构造出的命令执行,看不懂没关系,我们拆开一点点看。
new Transformer[]{new ConstantTransformer(Runtime.class)
这一句作用是把对象转换为一个恒定的值,在这里的作用就是无论原始映射中的值是什么,TransformMa 都会将其转换为 Runtime.class。这么做的好处就是增加了容错率和反序列化的成功率。
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]})
创建一个InvokerTransformer,它将调用Java运行时类的getMethod方法,参数值是getRuntime,表示要调用getMethod的名称
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
继续使用InvokerTransformer这次调用的是invoke,使用invoke把刚刚反射获取的getMethod实例化出来。
new InvokerTransformer("exec", new Class[]{String.class}, execArgs)
在invoke完成的基础上调用exec方法。
此时回想一下刚刚一系列的操作。先是设置恒定值Runtime.class。然后使用getMethod获取到他的getRuntime,使用invoke实例出来,最后调用实例化后的exec方法,所以这行代码最终结果就是
Runtime.getRuntime().exec
做个实验
看这段简易的代码,和yso的payload很类似。
`Runtime test = Runtime.getRuntime();`` ``InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});`` ``invokerTransformer.transform(test);`
功能很简单,test赋值Runtime.getRuntime()方便下面的操作成功获取。
使用InvokerTransformer传递了exec这个方法,并且它可以接受两个参数,把calc作为参数传递进去。
最后调用InvokerTransformer的transform方法,将先前创建的Runtime对象作为第一个参数传递。
执行结果就是成功弹出计算器。
总结一下其实InvokerTransformer其实就是把上一个处理的结果作为输入,并且用户可以对其进行自定义的操作,在我们的操作里就使用了反射获取了一个可被我们操控的命令执行语句。
那么现在payload有了我们继续看cc5的链子。
Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
进入LazyMap
发现其继承自Serializable可被序列化。并且存在了一个高危险的操作transform。去找谁调用了get方法。
根据链可知TiedMapEntry中调用了get这个方法。
那么现在逐渐清晰了,我们通过调用getval相当于间接调用了get这个方法,所以现在就是找哪里调用过getval
搜索可知equals,hashcode,toString都利用了getval方法。
那么还记得反序列化的必要条件吗,必须是有readobject,所以我们现在就来找哪里的readobject使用了这三个方法。
根据链子我们找到了BadAttributeValueExpException,发现其使用了readobject,并且使用了tostring方法。至此cc5的链就完美的形成了。
总结一下,BadAttributeValueExpException.readObject调用了tostring方法,相当于简介调用了TiedMapEntry.getValue,然而TiedMapEntry.getValue中调用了LazyMap.get方法。这个方法中存在了transform这个高危方法可使我们通过反射的方式构造一条命令执行语句出来。
在调试中可以发现TiedMapEntry中并非只有tostring调用了getval
所以其实这三个位置都有可发展空间,比如hashcode,是不是很眼熟???就留给大家自己发散思维吧。