前面我们学习了JAVA原生反序列并通过一个小靶场体验了代码分析-->POC编写-->白盒寻找gadgets chain-->利用反序列化getshell这样的一系列流程。还学习了JAVA安全中很重要的反射机制。
概括来说,JAVA原生序列化就是利用Java.io.ObjectOutputStream对象输出流的writeObject(Object obj) 方法将一个实现了Serializable或者Externalizable接口的类的对象转化为字节序列;而JAVA原生反序列化就是利用Java.io.ObjectInputStream对象输入流的readObject() 方法将一个字节序列恢复为对象。java原生序列化数据的16进制以aced0005开头,base64编码是以rO0AB开头。开发者通常会重写writeObject、readObject方法来自定义序列化与反序列化过程。关于反序列化的安全问题从根本上来说得从readObject()方法做文章,反序列化产生漏洞的形式有以下四种方式:
1.入口类的readObject直接调用危险方法(很理想的状态,多见于自娱自乐)
2.入口类参数中包含可控类,该类有危险方法,readObject时调用
3.入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用
4.构造函数/静态代码块等类加载时隐式执行
JAVA反射机制原理是由于JAVA类加载机制的特点,当进行类加载时会在堆区产生该类的Class对象(有且只有一个,可以理解为复制出来的该类的原型),该对象包含了类的完整结构信息(属性、方法、内部类等),它是一种数据结构,可通过一系列API获取类的完整结构。反射的作用:
使得JAVA语言具有动态性
修改已有对象的属性
动态生成对象
动态调用方法
操作内部类和私有方法
反射淋漓尽致地体现了JAVA语言万物皆对象的特点,在反射面前一切都是纸老虎。突破限制,这就是JAVA反射。
0x02 如何寻找Gadget的几点思考
共同条件:实现Serializable或者Externalizable接口,最好是jdk自带或者JAVA常用组件里有
入口类source:(重写readObject 调用常见函数 参数类型宽泛 最好jdk自带)
调用链gadget chain:相同方法名、相同类型
执行类sink:RCE SSRF 写文件等等
0x03 URLDNS链的发现与分析
1.URLDNS链介绍
URLDNS是JAVA复杂的反序列化链中最简单的一条,它不是一条真正意义上的“利⽤链”。因为它所能产生的结果不是命令执⾏,⽽是⼀次DNS请求。既然不能利用,那么为什么我们还要学习它呢?
第一,因为它足够简单,很适合新手,反序列化链的学习不建议一上来就分析CC链;第二,因为它使⽤Java内置的类构造,对第三⽅库没有依赖,对JDK版本没有要求,且其结果是发起一次DNS请求,不管目标有无回显,都⾮常适合用来检测是否存在反序列化漏洞。
2.URLDNS链的发现
根据上面对入口类的描述,对JAVA比较熟悉的话,首先就会想到Map系列(就是Python里的字典,是键值对),天然的理想的入口类,Map里常见的就是HashMap了。我们来看下HashMap的readObject()方法:
我们重点关注的应该是最后这几行代码:
这几行代码是循环读取键值对,然后利用putVal()方法将其存入HashMap中,而putVal()方法中又对键值执行了hash()方法,我们ctrl+b进入这个方法:
可以看到该方法需要传入的key的参数类型为Object即任意类的对象,我们很容易就可以套一个娃进去。该方法是判断键值是否为空,为空返回0,不为空则会对键值执行hashCode()即计算hash值。而hashCode()是很常见的方法,所有继承了Object的类都会有这个方法。
接下来如果我们要找调用链上的类,选择就很多了,jdk自带的实现了Serializable或者Externalizable接口的类有很多。毫无目的地从调用链到最后的执行某个操作这样正向找肯定很难,我们逆着来。先思考下我们想要产生的结果,首选当然是RCE,JAVA中执行代码相关的语句就是Runtime.getRuntime().exec()了,但Runtime这个类没有实现Serializable或者Externalizable接口:
挖RCE的想法gg。退而求其次,找SSRF,也即发起一个URL请求。jdk中与此相关的类就是java.net.URL了。我们来看下URL这个类:
实现了Serializable接口,可以作为执行类。其openConnection()方法可以发起URL请求:
但该方法并不是常用方法,我们逆着去找谁调用了这个方法也不好找。那我们来看下URL的hashCode()方法,看下假如我们传入的键值是URL对象会发生什么:
会先判断当前URL对象的hashCode是否为-1,不为-1则直接返回hashCode,如果为-1,则进入到handler.hashCode()方法。这里的-1为URL对象hashCode 的初始值,handler为URLStreamHander 对象,且带有transient,即不参与序列化:
我们继续跟进handler.hashCode():
调用了getHostAddress()方法,看方法名是获取主机地址,继续跟进:
看到InetAddress.getByName(host),看方法名是根据主机名字获取网络地址,这不就是一个DNS请求么?跟到这里就不需要再跟了。这就是URLDNS链了。
3.分析
根据上面的分析,我们来捋捋,最后的DNS请求是怎么触发的:
首先,HashMap的readObject()方法会循环读取键值对,利用putVal()方法将其存入HashMap中,而putVal()方法中又对键值执行了hash()方法,而hash()方法中若键值不为空,则会调用key的hashCode方法。其中,key的类型是Object即可以是任意对象,假如此处key的类型为URL对象,则会进入到URL的hashCode()方法,在该方法中若hashCode为初始值-1,会进入到handler.hashCode()方法重新计算hashCode(handler为URLStreamHander 对象),handler.hashCode()中会调用getHostAddress()方法,该方法会使用InetAddress.getByName(host)方法即根据主机名字获取网络地址,从而触发了DNS请求。
上面文字描述的URLDNS gadget可以这样表示:
0x04 POC编写
根据上面的分析,我们来编写POC。首先我们得要创建一个URL对象,此时其hashCode值为-1;再创建一个HashMap对象,将url对象作为键值传进去,值任意。此处有坑,我们使用put方法向hashMap传入值时,会发生:
看到putVal()和hash()是不是很熟悉,put时url对象的hashCode为-1,由此会进入到触发DNS请求的那个逻辑,也就是说我们在put时就会产生DNS请求:
而经过DNS请求之后,url对象的hashCode值就被重新计算,不是初始值-1了。那么接下来序列化时不是-1,反序列化出来自然也不是-1,故实际反序列化时也就不会进行DNS请求。而在我们的DNS平台上收到的请求实际上是put时发出的,会对我们判断目标是否存在反序列化漏洞形成干扰。
故此坑的解决方法是:利用反射,在put之前将url对象的hashCode值设值成不为-1,put之后再利用反射,将其设置为-1。这样put时就不会发起DNS请求了:
然后将其序列化,再反序列化,反序列化时收到DNS请求:
完整代码如下:
我们可以把这段POC修改下,使其生成Payload,将Payload放到之前的webgoat靶场中,DNSLog上收到请求:
是不是很有趣,哈哈~
0x05 总结
本文我们学习了最简单的一条Gadget—URLDNS链。从如何发现这条链的角度详细代码分析了该链的调用过程,以及最后的POC编写。其中使用到了反射机制,通过反射修改已有对象属性来解决put时的坑。
该链的特点是jdk自带,且无jdk版本限制,该链的作用是检测目标是否存在反序列化漏洞。可能有的读者会有疑问,HashMap为什么要重写readObject方法呢,有默认的反序列化不行么?感兴趣的可以看下这篇文章:
https://juejin.cn/post/6844903954774491144#heading-5
利用URLDNS检测出了漏洞,自然就要进行利用了。下一篇我们就来学习经典的CC1链。
Java安全系列文集
第6篇:JAVA安全|基础篇:反射机制之常见ReflectionAPI使用
如果喜欢小编的文章,记得多多转发,点赞+关注支持一下哦~,您的点赞和支持是我最大的动力~
原文始发于微信公众号(沃克学安全):JAVA安全|Gadget篇:URLDNS链
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论