前言
每次提到Java反序列化,我始终都能在脑海中浮现commons-collections,这一个能助开发从业者轻松开发的依赖,使人爱也萧何恨也萧何。今日与君再忆,望与君温故而知新。
序列化
定义
序列化:Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型.
反序列化:将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。
整个过程都是 Java 虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。
可以简单的说,序列化就是将内存中的数据持久化到硬盘中的过程.反序列化就是将硬盘中的数据恢复到内存中的过程。
提供序列化的API
说到序列化需要提到两个高层次的数据流
输出流:ObjectOutputStream
输入流:ObjectInputStream
其中ObjectOutputStream流提供的writeObject()序列化一个对象,并将它发送到输出流。public final void writeObject(Object x) throws IOException
其中ObjectInputStream流提供的readObject()从流中取出下一个对象,并将对象反序列化。它的返回值为Object,因此,你需要将它转换成合适的数据类型。public final Object readObject() throws IOException, ClassNotFoundException
序列化的应用场景
举例:tomcat服务器会在服务器关闭时把session序列化存储到tomcat目录一个名为session.ser的文件中,这个过程成为session的钝化,因为有些时候当我们要重新部署项目的时候,有的用户可能在访问,这样做的目的是服务器重启之后tomcat可以反序列化这个session.ser文件,将session对象重新生成出来,用户可以使用部署之前的session进行操作,这个反序列化的过程成为session的活化。
1.一切存储
2.一切传输
3.一切交互接口
代码进行序列化
为了演示序列化在Java中是怎样工作的,我将使用之前教程中提到的Employee类,假设我们定义了如下的Employee类,该类实现了Serializable 接口。
请注意,一个类的对象要想序列化成功,必须满足两个条件:
1.该类必须实现 java.io.Serializable 接口。
2.该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。
序列化的实现
反序列化的实现
反射
定义
反射就是在JAVA的运行状态的时候,可以对任何一个类进行操作,这是一个可以动态的获取信息和动态的使用某个方法的能力我们就叫他反射,看下名字就知道这项功能有多屌。
反射实现
先看在java中执行系统命令的方法
通过反射实现java中执行系统命令的方法
commons-collections-3.1反序列化漏洞
导入POM依赖
分析
这是网上利用cc链的payload
分析InvokerTransformer类,寻找恶意代码被执行的关键
上述方法创建了一个执行链条,通过反射的方法获取到Runtime类执行恶意代码Transformer transformerChain = new ChainedTransformer(transformers);
此代码可以理解为上述的执行链条被放入到一个对象中.此对象暂且命名为transformerChainMap innerMap = new HashMap();
innerMap.put("value", "value");
使用多态创建一个HashMap,并向其中塞入一个键值.Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
注意此行代码,着重分析下TransformedMap类中decorate方法作用:将上文塞入一个键值对的暂且命名为innerMap的Map,和上文将一套执行链塞入暂且命名为transformerChain的对象作为参数传入。并返回一个暂且名为outerMap的具有执行链对象的Map对象.此函数将原始map绑定具有执行链的对象,返回一个map。如果返回的map中的键值发生变化时将会调用执行链.造成了恶意代码执行。
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
onlyElement.setValue("雁不过衡阳");
注意此行代码,使用迭代器对上文中具有执行链对象的Map对象进行遍历,并获取第一个键值对,然后对其值进行修改.当修改此键时就触发执行链。
跟进setValue()方法查看调用执行链的根本原因
进入checkValue()方法发现本质上还是调用了valueTransformer也就是执行链
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
进入checkSetValue()发现调用执行链,遂改变map值可以触发代码执行.
但是这么做有一个缺点,就是在应用中,反序列化时需要一个对象,但是这些操作无法被打包成对象进行,所以还需要一个寄生对象,这个对象需要满足
他的反序列化方法readObject()需要对一个map进行操作,从而可以调用执行链。
找到一个载体
在java中,自带的类中还有一个类叫做AnnotationInvocationHandler
该类中重写的readObject方法在被调用时会将其中的map,转成Map.Entry,并执行setValue操作,那么能把TransformedMap装入这个AnnotationInvocationHandler类,再传过去,就可以不用考虑之后代码是否执行setValue就可以直接利用漏洞了
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class, transformedMap);
注意此段代码,使用反射将sun.reflect.annotation.AnnotationInvocationHandler类反射出来,在将此类中的构造函数反射出来方便后期new对象,解除安全模式,采用暴力反射所以设置为true.然后调用newInstance获取此对象,将上文获得的带有执行链的map作为有参构造传入.
进入AnnotationInvocationHandler代码查看触发原因
在有参构造创建对象需要将类class和一个map对象传入,map对象又赋值给了全局变量memberValues
全局变量memberValues赋值给了var4,var4给了var5,最后var5调用setValue()重新对map进行修改.回归当初,在我们new这个对象时memberValues就是带有执行链的map,所以值被修改所以执行链触发导致代码执行。
在1.8中这个类的readObject()方法重新赋值变为var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
不再对map进行遍历修改值,所以无法触发执行链。
使用rmi服务模拟攻击与被攻击端深入体会攻击原理
创建User接口继承Remote服务
创建实现类实现User接口,实现test方法
监听9090端口开启rmi服务
请求服务端的rmi服务地址,将利用封装至找到的载体当中,将载体对象发送至rmi的test()方法
整个过程模拟攻击端和服务端,服务端开启rmi服务,攻击端将带有恶意代码的载体发送至服务端,服务端再对载体进行反序列化时修改了map中的值触发了恶意代码的执行.
总结
Java反序列化一直是一个 老生常谈的问题,理解这些原理性的知识可以更好的帮助我们找到执行链,你我终有一天也会发现理解事物的本质是如此重要。
发表评论
您还未登录,请先登录。
登录