ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Activiti BPMN流程引擎使用不当导致的相关RCE问题

2022-05-14 09:34:05  阅读:322  来源: 互联网

标签:调用 Activiti 流程 bpmn 引擎 RCE processEngine BPMN


PS:首发自:https://moonsec.top/articles/82

说明

此篇文章主要记录Activiti流程引擎在使用过程中,使用不当会造成的相关问题以及RCE方法,此篇仅做安全研究用,无用相关的攻击,否则后果自负。

1.Activiti说明

1.1 概念

工作流。通过计算机对业务流程自动化执行管理,主要解决的是“使在多个参与者之间按照某种预定义的规则自动进行传递文档、信息或任务的过程,从而实现某个预期的业务目标,或者促使此目标的实现”。

1.2 相关的说明

具体的activiti的相关说明参考如下的链接:Activiti工作流
https://blog.csdn.net/Mr_97xu/article/details/112899079

1.3 流程引擎配置类

流程引擎配置类(ProcessEngineConfiguration),通过 ProcessEngineConfiguration 可以创建工作流引擎 ProceccEngine。
工作流引擎的创建
工作流引擎的创建主要有两种方式:默认创建方式和一般创建方式
默认创建方式

ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
System.out.println(processEngine);

一般创建方式

//使用自定义方式创建
ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
//获取流程引擎对象:通过 ProcessEngineConfiguration 创建 ProcessEngine,此时会创建数据库
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();

当创建好工作流引擎后,对应的数据库中会自动生成25张数据库表。
image.png

ACT_GE_PROPERTY中会先展示下一次流程的ID(next.dbid),并且在下一次流程部署的时候,对下一次流程的ID进行赋值。
image.png

1.4 Activiti表说明

这里以表名的前缀进行说明:
image.png
Service服务接口
Activiti中还有许多的Service服务接口。这些Service 是工作流引擎提供用于进行工作流部署、执行、管理的服务接口,我们可以使用这些接口操作服务对应的数据表。

Service创建方式
通过ProcessEngine创建Service方式:

Runtimeservice runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();

image.png

RepositoryService

Activiti 的资源管理类,提供了管理和控制流程发布包和流程定义的操作。使用工作流建模工具设计的业务流程图需要使用此service将流程定义文件的内容部署到计算机。除了部署流程定义以外,还可以查询引擎中的发布包和流程定义。暂停或激活发布包,对应全部和特定流程定义。暂停意味着它们不能再执行任何操作了,激活是对应的反向操作。获得多种资源,像是包含在发布包里的文件,或引擎自动生成的流程图。获得流程定义的pojo版本,可以用来通过java解析流程,而不必通过xml。

Runtimeservice
Activiti的流程运行管理类。可以从这个服务类中获取很多关于流程执行相关的信息

Taskservice
Activiti的任务管理类。可以从这个类中获取任务的信息。

Historyservice
Activiti的历史管理类,可以查询历史信息,执行流程时,引擎会保存很多数据(根据配置),比如流程实例启动时间,任务的参与者,完成任务的时间,每个流程实例的执行路径,等等。这个服务主要通过查询功能来获得这些数据。

ManagementService
Activiti的引擎管理类,提供了对Activiti流程引擎的管理和维护功能,这些功能不在工作流驱动的应用程序中使用,主要用于Activiti 系统的日常维护。

1.5 流程符号、画流程图

可以通过idea 的BPMN 插件来进行绘制。
image.png

1.6 流程的操作

1.6.1 部署流程

使用 Activiti 提供的 API 把流程图的内容写入到数据库中
属于资源操作类,使用 RepositoryService
单文件部署:把bpmn文件和png文件逐个处理
压缩包部署:把bpmn文件和png文件打成压缩包来处理
部署操作表:act_re_deployment、act_re_procdef、act_ge_bytearray

/**
 * 流程部署
 */
public void deployment() {
    // 创建 ProcessEngine
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取 RepositoryService
    RepositoryService repositoryService = processEngine.getRepositoryService();
    // 使用 service 进行流程的部署,定义一个流程的名字,把bpmn和png部署到数据中
    Deployment deployment = repositoryService.createDeployment()
            .name("出差申请流程")	//流程图标的名字
            .addClasspathResource("bpmn/evection.bpmn")	//bpmn文件
            .addClasspathResource("bpmn/evection.png")	//bpmn文件生成的图片
            .deploy();
    // 输出部署信息
    System.out.println("流程部署ID:" + deployment.getId());
    System.out.println("流程部署名字:" + deployment.getName());
}

操作的数据库表:
act_ge_bytearray、act_ge_property、act_re_deployment、act_re_procdef

1.6.2 启动流程实例

流程部署完成以后,需要启动流程实例。使用 RuntimeService 根据流程定义的 key进行启动。
核心代码:

/**
 * 启动流程
 */
public void starProcess() {
    // 创建 ProcessEngine
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取 RunTimeService
    RuntimeService runtimeService = processEngine.getRuntimeService();
    // 根据流程定义的ID启动流程
    ProcessInstance instance = runtimeService.startProcessInstanceByKey("myEvection");
    // 输出内容
    System.out.println("流程定义ID:" + instance.getProcessDefinitionId());
    System.out.println("流程实例的ID:" + instance.getId());
    System.out.println("当前活动的ID:" + instance.getActivityId());
}

1.6.3 任务查询

使用 TaskService ,根据流程定义的 key ,任务负责人来进行查询
核心代码:

/**
 * 查询个人待执行的任务
 */
@Test
public void findPersonalTaskList() {
    // 获取流程引擎
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    // 获取TaskService
    TaskService taskService = processEngine.getTaskService();
    // 根据流程的key和任务的负责人去查询任务
    List<Task> taskList = taskService.createTaskQuery()
            .processDefinitionKey("myEvection")  // 流程的key
            .includeProcessVariables()
            .taskAssignee("zhangsan")           // 要查询的负责人
            .list();
    // 输出
    for (Task task : taskList) {
        System.out.println("流程实例的ID:" + task.getProcessInstanceId());
        System.out.println("任务的ID:" + task.getId());
        System.out.println("任务的负责人:" + task.getAssignee());
        System.out.println("任务的名称:" + task.getName());
    }
}

1.6.4 任务完成

使用 TaskService ,用任务 ID 直接完成任务。
核心代码:

/**
 * 完成个人任务
 */
@Test
public void completTask() {
    String key = "testCandidiate";
    String assignee = "张三1";	//任务的负责人
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    TaskService taskService = processEngine.getTaskService();
    Task task = taskService.createTaskQuery()
            .processDefinitionKey(key)
            .taskAssignee(assignee)
            .singleResult();
    if (task != null) {
        taskService.complete(task.getId());
    }
}

2. Activiti 的漏洞点

实际上使用Activiti的场景主要有2中情况:
1、产品根据业务需求使用bpmn进行开发,最终发的时候内置bpmn在发布包中,不允许用户自行定义。
2、产品使用提供Activiti的通用能力,运行用在产品上自己编辑定义bpmn流程,并执行该流程。
一般来说,第1中情况不存在问题,下面主要套路第2种情况。
Activiti涉及到的漏洞点有主要以下几种:

  • ScriptTask: ScriptTaskActivityBehavior中使用ScriptEngine

3. Activiti的漏洞点

3.1 ScriptTask

ScriptTast看相关的bpmn配置demo如下:

 <process id="hireProcessWithJpa" name="Developer Hiring" isExecutable="true">
    <startEvent id="sid-E0DD2D8E-0672-4BE0-97A4-933DD8771EFF"/>
    <scriptTask id="sid-6b441d89-8564-4069-bb06-fbce3cb9da37" name="scriptTest" scriptFormat="js" activiti:resultVariable="a">
      <script>a=java.lang.Runtime.getRuntime().exec('calc')</script>
    </scriptTask>
    <sequenceFlow id="sid-228a0741-8bf0-4603-9d25-19943b3917d8" sourceRef="sid-E0DD2D8E-0672-4BE0-97A4-933DD8771EFF" targetRef="sid-6b441d89-8564-4069-bb06-fbce3cb9da37"/>
    <endEvent id="sid-90bb0d22-d2d4-4eb6-9a6d-b23f2cdc8688"/>
    <sequenceFlow id="sid-71f0cd02-3d3a-45e2-98a3-349ccec4b3e5" sourceRef="sid-6b441d89-8564-4069-bb06-fbce3cb9da37" targetRef="sid-90bb0d22-d2d4-4eb6-9a6d-b23f2cdc8688"/>
  </process>

image.png
触发漏洞的点在于:

    <scriptTask id="sid-6b441d89-8564-4069-bb06-fbce3cb9da37" name="scriptTest" scriptFormat="js" activiti:resultVariable="a">
      <script>a=java.lang.Runtime.getRuntime().exec('calc')</script>
    </scriptTask>

bpmn触发的接口类如下:

    @RequestMapping(value = "/start-hire-process", method = RequestMethod.POST,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public void startHireProcess(@RequestBody Map<String, String> data) {

        Applicant applicant = new Applicant(data.get("name"), data.get("email"), data.get("phoneNumber"));
        applicantRepository.save(applicant);

        Map<String, Object> vars = Collections.<String, Object>singletonMap("applicant", applicant);
        runtimeService.startProcessInstanceByKey("hireProcessWithJpa", vars);
        System.out.println("process finish");
    }

具体代码可以参考最后的git工程。

首先执行下该漏洞,同过接口调用,执行的结果如下:
image.png

3.1.1 相关的调试

首先看下相关的漏洞调用链。
在ScriptTaskActivityBehavior的scriptingEngines.evaluate(script, language, execution, storeScriptVariables) 打上断点接可查看全部的调用链过程。
整体的调用链如下:
image.png

3.1.3 调用链分析

1、通过 runtimeService.startProcessInstanceByKey 调用bpmn的xml配置文件,启新的流程实例。
runtimeService 是一个接口类,
image.png
在实现类中调用commandExecutor.execute 来执行
image.png
2、commandExecutor 也是一个接口类,最终会调用SpringTransactionInterceptor的execute方法
image.png
在execute方法方法中先初始化TransactionTemplate,然后通过编程事务模板管理来进行处理该流程。这么做的好处是,如果事务处理过程中遇到问题可以进行全面的回滚,将所有的状态回滚到开始的状态。
具体的编程式事务:可以参考https://blog.csdn.net/qq_33404395/article/details/83377382
3、接下来,通过CommandContextInterceptor中的execute方法执行
next.execute(config, command)
在此处的command为processDefinitionKey,即定义的bpmn的xml中的流程key。
4、然后调用TransactionContextInterceptor方法中的exec,调用TransactionContextInterceptor的目的是为了创建TransactionContext事务,用该事务,保存
ontext.getCommandContext()的变量信息,方便出错时候的回滚
image.png
5、上述的2次事务的创建方法完成后,后续进入真正的bpmn的执行流程。
CommandInvoker中调用executeOperations方法,该方法通过while循环读取bpmn的操作节点信息
image.png
image.png
6、通过DeployCmd的execute 方法来部署该流程
image.png
image.png
7、在DeploymentManager的deploy方法中逐个节点去部署
image.png
实际上部署的节点过程是通过BpmnDeployer的 createLocalizationValues方法来创建各个节点
image.png
8、在DefaultListableBeanFactory中将执行环境中所有的bean都进行初始化
image.png
9、在CommandInvoker中调用executeOperation run方法,在该方法中调用continueThroughFlowNode方法来执行各个节点
image.png
10、在ContinueProcessOperation中获取currentelement节点信息
image.png
11、在ContinueProcessOperation中通过executeSynchronous 来同步执行节点
image.png
12、ContinueProcessOperation中通过executeActivityBehavior 来执行 bpmn中xml中的scriptTask节点
image.png
13、最终在ScriptTaskActivityBehavior中执行该脚本
image.png
调用的是ScriptingEngines的eval方法。
14、至此scripttask执行完成
image.png

上述记录了scriptTask的全部流程。
PS:调试过程中发现activiti的流程比较长,对activiti的整体了解不够,写的内容可能会有问题,此篇先做流程的记录,后续使用过程中发现问题在同步刷新。

3.2 serviceTask

对应的相关配置
image.png
执行结果如下:
image.png
调试对应的调用栈
image.png
1、 在ContinueProcessOperation的executeActivityBehavior中执行xml中设置的serviceTask中定义的activiti class->org.activiti.engine.impl.bpmn.behavior.ShellActivityBehavior
image.png
为啥会在executeActivitiBehavior,可以参考 3.1的 12步骤“12、ContinueProcessOperation中通过executeActivityBehavior 来执行 bpmn中xml中的scriptTask节点”
2、在ClassDelegate中调用 activityBehaviorInstance.execute(execution);方法
image.png
3、直接调用了ShellActivityBehavior的exec方法,
image.png
直接在该类的execute方法中执行命令,
image.png
直接传递进去该类的三个参数,然后通过反射实例化进行运行。
image.png

3.2 TaskListener 方法

image.png

3.3 executionListener 方法

image.png

3.4 expression 方法

image.png

相关的调试代码

https://github.com/wangsz05/LearnDemo

标签:调用,Activiti,流程,bpmn,引擎,RCE,processEngine,BPMN
来源: https://www.cnblogs.com/TT0TT/p/16269111.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有