- 前言
- 动态类加载机制
- TemplatesImpl类加载实现任意代码执行
- cc1+TemplatesImpl
- cc6+TemplatesImpl
- InstantiateTransformer
- TrAXFilter
- 完整POC
前言
CC3这条链比较特殊,和CC1与CC6这两条链是直接在链的代码中执行任意代码相比,CC3是通过动态类加载机制来实现自动执行恶意类的代码的。因此需要先学习一下 Java 动态类加载机制
动态类加载机制
Java 是运行在 Java 的虚拟机(JVM)中的,但是它是如何运行在JVM中了呢?我们在IDE中编写的 Java 源代码被编译器编译成 .class
的字节码文件。然后由我们得由类加载器负责将这些 class 文件给加载到 JVM 中去执行。JVM提供了三层类加载器
Bootstrap classLoader:启动类加载器,主要负责加载核心的类库(java.lang.*等),构造 ExtClassLoader 和 APPClassLoader。
ExtClassLoader:拓展类加载器,主要负责加载 JAVA_HOME/lib/ext 目录下的一些扩展的 jar。
AppClassLoader:应用程序类加载器,主要负责加载应用程序的主函数类
加载类就是指从编译好的字节码.class
文件(所有能被恢复成一个类,并且能在 JVM 虚拟机中加载的字节序列)中加载类,不管是远程服务器上的字节码文件还是本地的字节码文件或者 jar 包。
首先调用 ClassLoader
类中的 loadClass
方法,从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,就会交给ClassLoader
类中 findClass
方法;然后findClass
方法根据基础路径指定的方式来加载类的字节码,可能会在本地文件系统、jar 包或远程 http 服务器上读取字节码,然后将字节码交给 defineClass
双亲委派机制
当一个Hello.class这样的文件要被加载时,不考虑我们自定义类加载器。首先会在 AppClassLoader 中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的 loadClass 方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达 Bootstrap ClassLoader 之前,都是在检查是否加载过,并不会选择自己去加载。直到 Bootstrap ClassLoader ,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出 ClassNotFoundException。
优势:
- 沙箱安全机制:比如自己写的 String.class 不被加载,防止核心类被随意篡改
- 避免类的重复加载:当父类加载器已经加载了该类的时候就不需要子类加载器加载了
可以见得真正核心的部分其实是 defineClass
,他决定了如何将一段字节流转变成一个 Java 类,Java 默认的 ClassLoader#defineClass
是一个 native方法(Java调用非Java代码的接口),逻辑在 JVM 的C语言代码中。
注意一点,在 defineClass()
被调用的时候,类对象是不会被初始化的,只有这个对象显式地调用其构造函数,初始化代码才能被执行。而且,即使我们将初始化代码放在类的 static 块中,在 defineClass()
时也无法被直接调用到。所以,如果我们要使用 defineClass()
在目标机器上执行任意代码,需要想办法调用构造函数 newInstance()
又因为ClassLoader#defineClass
这个方法是受保护的,无法直接在外部访问,需要使用反射的知识进行调用
1 | import java.lang.reflect.Method; |
运行后输出Hello World
综上所述,既然是我们想通过加载类来实现任意代码执行,所以就要找到一个重写了 defineClass()
方法的类,它的链上的某个类里面要调用了 newInstance()
方法,才能实现任意代码执行
TemplatesImpl类加载实现任意代码执行
在com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
这个类中定义了一个内部类TransletClassLoader
,而在这个类中重写了defineClass
方法,而且这个方法并没有显式地声明定义域,也就是说它是一种默认的类型,也就是default
类型的,而default
类型的是可以被类外部调用的
1 | static final class TransletClassLoader extends ClassLoader { |
因为TransletClassLoader
是内部类,所以需要找到TemplatesImpl
类中哪个方法调用了它,里面就有defineTransletClasses()
方法,但它是一个private
方法,并且在这里可以看到通过for
循环,依次加载字节码_bytecodes
中的内容,然后赋值给Class数组_class
1 | private void defineTransletClasses() |
继续找哪里调用了defineTransletClasses()
方法,就有getTransletInstance()
方法,可以看到将字节码加载进来之后,会执行_class[_transletIndex].newInstance()
,这里就会实例化类,执行任意代码了
1 | private Translet getTransletInstance() |
它也是private
方法继续找哪里调用了getTransletInstance()
方法,就有newTransformer()
方法,终于找到了可以直接调用的public
方法
1 | public synchronized Transformer newTransformer() |
这样调用链就出来了
1 | TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass() |
然后还需要设置好TemplatesImpl
对象的属性保证每步被正常调用到,因为都是私有属性,所以采用反射机制设置。
_bytecodes
是由字节码组成的数组;
在defineTransletClasses()
方法中if
会判断传入的字节码的父类要是ABSTRACT_TRANSLET
也就是需要继承这个包com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
所以我们还得构造一个特殊的类,用这个类生成字节码
_name
可以是任意字符串,只要不为 null 即可;
_tfactory
需要是一个TransformerFactoryImpl
对象;
因为TemplatesImpl#defineTransletClasses()
方法里有调用到_tfactory.getExternalExtensionsMap()
如果是null
会出错
1 | import com.sun.org.apache.xalan.internal.xsltc.DOM; |
编译这个类得到字节码后就可以写一个简单的手动执行的POC了
1 | import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; |
cc1+TemplatesImpl
也就是用InvokerTransformer
来调用newTransformer
这个方法
1 | import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; |
这里还能写成TransformedMap
版的cc1链,甚至于写成更通用的cc6链
cc6+TemplatesImpl
1 | import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; |
InstantiateTransformer
下面到了本文主角cc3链,即使前面已经有了强大的cc6链,但现在很多过滤器都会过滤掉InvokerTransformer
,这样之前的链子就打不通咯,所以得找个类代替它来绕过过滤,这个类就是org.apache.commons.collections.functors.InstantiateTransformer
InstantiateTransformer
类也是实现了Transformer
接口的一个类
1 | public class InstantiateTransformer implements Transformer, Serializable { |
可以看出它可以调用指定类得构造方法,也就是找到一个构造方法调用TransformerImpl
类中的newTransformer()
方法的类
TrAXFilter
com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter
这个类的构造方法中,就正好调用了(TransformerImpl) templates.newTransformer()
,且前面的Templates
对象可控,
1 | public class TrAXFilter extends XMLFilterImpl { |
这样就能利用InstantiateTransformer
的transform()
来调用到TrAXFilter
的构造方法,那么它就会自动调用templates.newTransformer()
,就可以加载我们放在TemplatesImpl
里面的字节码
所以Transformer
数组就可以改写成
1 | Transformer[] transformers = new Transformer[]{ |
完整POC
1 | import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; |
参考链接:
Author: ph0ebus