要理解Java反序列化漏洞需要一些Java的相关知识: 1.使用Java反射机制 2.利用sun.reflect.annotation.AnnotationInvocationHandler类 3.调用TransformedMap类的decorate方法时,参数一的Map对象需要put进”value”与非空的值 4.AnnotationInvocationHandler类的实例化参数一需要为java.lang.annotation.Retention类 下面先介绍基础知识,再利用类TransformedMap与AnnotationInvocationHandler分析漏洞。
Java序列化与反序列化 Java序列化:使用ObjectOutputStream.writeObject方法可对实现了Serializable接口的对象进行序列化,序列化的数据可存储在文件中,或通过网络传输。 Java反序列化:使用ObjectInputStream.readObject方法可对序列化的数据进行反序列化。当实现了Serializable接口的对象被反序列化时,该对象的readObject方法会被调用。 String实现了Serializable接口,可进行序列化。以下测试代码对String类的对象进行序列化,将序列化的数据保存在文件中,再从文件读取序列化的数据进行反序列化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Basic { public static void main (String[] args) throws IOException, ClassNotFoundException { String test = "tttest" ; FileOutputStream fos = new FileOutputStream("test.ser" ); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(test); os.close(); FileInputStream fis = new FileInputStream("test.ser" ); ObjectInputStream ois = new ObjectInputStream(fis); String stringFromData = (String)ois.readObject(); ois.close(); System.out.println(stringFromData); } }
Java序列化数据的magic number java.io.ObjectStreamConstants类中定义了STREAM_MAGIC与STREAM_VERSION,查看JDK1.5、1.6、1.7、1.8的ObjectStreamConstants类,STREAM_MAGIC值均为0xaced,STREAM_VERSION值均为5。即0xaced为Java对象序列化流的魔数,0x0005为Java对象序列化的版本号,Java对象序列化数据的前4个字节为”AC ED 00 05”。 查看上一步生成的文件:
对自定义类的序列化与反序列化 下面的测试代码为SerializeMyClass类,在其中定义了内部类MyObject。MyObject类实现了Serializable接口,SerializeMyClass类会对MyObject类的对象进行序列化,将序列化的数据保存在文件中,再从文件读取序列化的数据进行反序列化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 package test;public class SerializeMyClass { public static void main (String[] args) throws Exception { MyObject myObj = new MyObject("tttest" ); FileOutputStream fos = new FileOutputStream("test-myobj.ser" ); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(myObj); os.close(); FileInputStream fis = new FileInputStream("test-myobj.ser" ); ObjectInputStream ois = new ObjectInputStream(fis); MyObject objectFromData = (MyObject) ois.readObject(); ois.close(); System.out.println(objectFromData.getName()); } } class MyObject implements Serializable { private static final long serialVersionUID = 1L ; private String name = "initial" ; public MyObject () { this .name = "no name-default" ; System.out.println("MyObject(String name) " + name); } public MyObject (String name) { this .name = name; System.out.println("MyObject(String name) " + name); } public String getName () { return this .name; } private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); System.out.println("MyObject-readObject!!!! " + this .name); this .name = this .name + "!" ; } }
程序输出:
1 2 3 MyObject(String name) tttest MyObject-readObject!!!! tttest tttest!
可以看到MyObject类实现的Serializable接口的readObject方法会被调用,且对象被序列化再反序列化后,对其值没有影响。
Java反射机制 之前写过一篇Java反射机制 的文章,不过只是一些理论类的东西,跟文档差不多,这里实际使用一下。 (1)调用FileOutputStream类写文件时,常用代码如下:
1 2 FileOutputStream fos = new FileOutputStream("1.txt" ); fos.write("abc" .getBytes());
若需要使用Java反射机制调用FileOutputStream类写文件,且只允许调用Class.getMethod与Method.invoke方法,需要使用如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class ReflectBasic { public static void main (String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Object object = FileOutputStream.class; Class cls = object.getClass(); Method method = cls.getMethod("getConstructor" , new Class[] { Class[].class }); object = method.invoke(object, new Object[] { new Class[] { String.class } }); cls = object.getClass(); method = cls.getMethod("newInstance" , new Class[] { Object[].class }); object = method.invoke(object, new Object[] { new Object[] { "./1.txt" }}); cls = object.getClass(); method = cls.getMethod("write" , new Class[] { byte [].class }); object = method.invoke(object, new Object[] {"abc" .getBytes()}); } }
(2)使用Java反射机制调用Runtime类执行程序,常用代码如下:
1 2 Runtime runtime = Runtime.getRuntime(); runtime.exec("calc" );
如果使用Java反射机制调用Runtime类执行程序,且只允许调用Class.getMethod与Method.invoke方法,上述代码需修改为如下形式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Object object = Runtime.class; Class cls = object.getClass(); Method method = cls.getMethod("getMethod" , new Class[] { String.class, Class[].class }); object = method.invoke(object, new Object[] { "getRuntime" , new Class[0 ] }); System.out.println("-1- " + object.getClass()); cls = object.getClass(); method = cls.getMethod("invoke" , new Class[] { Object.class, Object[].class }); object = method.invoke(object, new Object[] { null , new Object[0 ] }); cls = object.getClass(); method = cls.getMethod("exec" , new Class[] { String.class }); object = method.invoke(object, new Object[] { "/usr/bin/open /Applications/Notes.app" });
Java反射机制与序列化 当需要操作无法直接访问的类时,需要使用Java的反射机制。即对无法直接访问的类进行序列化时,需要使用Java的反射机制。 以下测试代码为testreflection.TestReflection类,与前文中的test.MyObject类不在同一个包中,在TestReflection类中对MyObject类进行序列化时,需要使用Java的反射机制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package testreflection;public class TestReflection { public static void main (String args[]) throws Exception { doSerialize("tttest" ); doSerialize(null ); } private static void doSerialize (String data) throws Exception { Class cls = Class.forName("test.MyObject" ); Constructor ctor = null ; Object instance = null ; if (data != null ) ctor = cls.getDeclaredConstructor(new Class[] { String.class }); else ctor = cls.getDeclaredConstructor(); ctor.setAccessible(true ); System.out.println("-before newInstance-" ); if (data != null ) instance = ctor.newInstance(data); else instance = ctor.newInstance(); System.out.println("-after newInstance-" ); ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteOut); out.writeObject(instance); out.flush(); out.close(); System.out.println("byteOut.toByteArray().length:" +byteOut.toByteArray().length); ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray()); ObjectInputStream ois = new ObjectInputStream(byteIn); Object object = (Object)ois.readObject(); System.out.println("object:" + object.getClass()); } }
程序执行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 -before newInstance- MyObject(String name) tttest -after newInstance- byteOut.toByteArray().length:71 MyObject-readObject!!!! tttest object:class test.MyObject -before newInstance- MyObject(String name) no name-default -after newInstance- byteOut.toByteArray().length:80 MyObject-readObject!!!! no name-default object:class test.MyObject
基础知识就介绍到这里,后面会再进行漏洞分析的学习。reference JAVA反序列化漏洞完整过程分析与调试