前言
学习 Java 反序列化利用链的过程中经常出现到 TemplatesImpl 的身影,这里简单分析一下它的作用以及调用链。
ClassLoader 加载字节码
我们都知道 Java 的 ClassLoader 是用来加载字节码文件最基础的方法,ClassLoader 是什么呢?它就是一个“加载器”,告诉Java虚拟机如何加载这个类,用一句话概括它的作用就是将传入的字节码处理成真正的 Java 类然后返回。
ClassLoader 处理字节码的流程为 loadClass -> findClass -> defineClass
loadClass: 从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass
findClass: 根据基础URL指定的方式来加载类的字节码
defineClass:处理前面传入的字节码,将其处理成真正的Java类
所以将字节码转为 java 类的其实是 defineClass 方法, 翻看源码 ClassLoader#defineClass 是一个protected属性,无法直接在外部访问,只能通过反射的形式来调用。所以在实际场景中很难利用到它。
利用 TemplatesImpl 加载字节码
前边说到 ClassLoader 的 defineClass 方法只能通过反射调用,在实际环境中很难有利用场景。但是在 TemplatesImpl 类中有一个内部类 TransletClassLoader 它重写了 defineClass,并且这里没有显式地声明其定义域。Java中默认情况下,如果一个方法没有显式声明作用域,其作用域为default。所以也就是说这里的 defineClass 由其父类的 protected 类型变成了一个 default 类型的方法,可以被类外部调用。
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
但是 TransletClassLoader 是内部类,只允许 TemplatesImpl 类中的方法调用,我们跟一下有哪些方法用到了 TransletClassLoader。
TemplatesImpl 类中只有一个方法 TemplatesImpl#defineTransletClasses 用到了 TransletClassLoader 类,但是 TemplatesImpl#defineTransletClasses 是 private 类型。继续跟看哪调用了 TemplatesImpl#defineTransletClasses 方法。
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
有三个方法调用了 TemplatesImpl#defineTransletClasses(), 其中 TemplatesImpl#getTransletIndex() 是 public 类型的,TemplatesImpl#getTransletClasses() 和 TemplatesImpl#getTransletInstance() 是 private 属性的。
1 | private synchronized Class[] getTransletClasses() |
尝试用 TemplatesImpl#getTransletIndex() 当作入口去调用 defineClass
1 | TemplatesImpl#getTransletIndex() -> TemplatesImpl#defineTransletClasses() -> TemplatesImpl#defineTransletClasses() -> defineClass |
这里用p牛文章中的dome,发现没用成功。
这里原因暂时没搞清楚,继续跟 TemplatesImpl#getTransletClasses() 和 TemplatesImpl#getTransletInstance()
TemplatesImpl 类中已经没有调用 getTransletClasses() 的方法了,而 getTransletInstance() 方法在 public synchronized Transformer newTransformer 方法中被调用了
所以到这又有一条调用链
1 | TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()->TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass() |
成功触发了HelloTemplatesImpl的构造函数,p牛文章中的调用链就是这个。
再继续跟进 TemplatesImpl#newTransformer() 发现 TemplatesImpl#getOutputProperties() 调用了 TemplatesImpl#newTransformer() 并且它也是 public 类型的
1 | TemplatesImpl#getOutputProperties() ->TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()->TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass() |
也成功触发了。
总结一下三条调用链
1 | //未成功触发 |
1 | TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()->TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass() |
1 | TemplatesImpl#getOutputProperties() ->TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()->TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass() |
为什么 getTransletIndex -> defineTransletClasses->defineTransletClasses -> defineClass 这条链不行呢,分别 debug 调试一下三条调用链发现最终到达 defineClass 的时候变量b 的值是一样的。请教了一下 P牛 ,发现这和defineClass() 的特性有关。在defineClass
被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的static块中,在defineClass
时也无法被直接调用到。所以,如果我们要使用defineClass
在目标机器上执行任意代码,需要想办法调用构造函数。回过来看调用的getTransletIndex ()方法,虽然有执行到defineClass,但后面没有对这个类对象进行实例化,也就是没有调用构造函数,所以其实是没有触发漏洞的。而TemplatesImpl#newTransformer(),在调用完TemplatesImpl#defineTransletClasses()后又调用了newInstance()构造函数(如图),所以触发了恶意代码,
ysoserial 中的 Gadgets.createTemplatesImpl 函数, 其实就是生成一个 TemplatesImpl 去加载字节码从而实现执行命令
1 | public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory ) |
参考文章
https://blog.csdn.net/fnmsd/article/details/88543233
p牛 《Java安全漫谈 - 13.Java中动态加载字节码的那些方法》