最近的Activiti 5.21.0版本的突出特点之一是“安全脚本”。 Activiti用户指南中详细介绍了启用和使用此功能的方法 。 在这篇文章中,我将向您展示我们如何实现其最终实现以及它在幕后所做的事情。 当然,由于这是我通常的签名风格,因此我们还将对性能进行一些了解。

问题

长期以来,Activiti引擎一直支持脚本任务(和任务/执行侦听器)的脚本编写。 所使用的脚本在流程定义中定义,并且可以在部署流程定义后直接执行。 这是很多人喜欢的东西。 这与Java委托类或委托表达式有很大的不同,因为它们通常需要将实际的逻辑放在类路径上。 它本身已经引入了某种“保护”,因为高级用户通常只能这样做。

但是,使用脚本不需要这种“额外步骤”。 如果您将脚本任务的功能提供给最终用户(并且我们从某些用户那里知道某些公司确实拥有此用例),那么所有的赌注都将大打折扣。 您可以通过执行流程实例来关闭JVM或执行恶意操作。

第二个问题是编写一个无限循环且永无止境的脚本非常容易。 第三个问题是,脚本在执行时可以轻松使用大量内存,并占用大量系统资源。

让我们来看入门的第一个问题。 首先,让我们添加最新和最大的Activiti引擎依赖性以及内存数据库库中的H2:

<dependencies><dependency><groupId>org.activiti</groupId><artifactId>activiti-engine</artifactId><version>5.21.0</version></dependency><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><version>1.3.176</version></dependency>
</dependencies>

我们将在这里使用的过程非常简单:只是一个开始事件,脚本任务和结束。 此处的过程并不是真正的重点,而是脚本执行。

我们将尝试的第一个脚本有两件事:它会获取并显示我的机器的当前网络配置(但显然有此想法的更危险的应用程序), 然后关闭整个JVM 。 当然,在适当的设置中,可以通过确保运行逻辑的用户在计算机上没有任何重要权限(但不能解决占用资源的问题)来缓解其中的某些问题。 但是我认为这很好地说明了为什么将脚本的功能提供给几乎任何人实际上在安全方面是很糟糕的。

<scriptTask id="myScriptTask" scriptFormat="javascript"><script>var s = new java.util.Scanner(java.lang.Runtime.getRuntime().exec("ifconfig").getInputStream()).useDelimiter("\\A");var output = s.hasNext() ? s.next() : "";java.lang.System.out.println("--- output = " + output);java.lang.System.exit(1);</script>
</scriptTask>

让我们部署流程定义并执行流程实例:

public class Demo1 {public static void main (String[] args) {// Build engine and deployProcessEngine processEngine = new StandaloneInMemProcessEngineConfiguration().buildProcessEngine();RepositoryService repositoryService = processEngine.getRepositoryService();repositoryService.createDeployment().addClasspathResource("process.bpmn20.xml").deploy();// Start process instanceRuntimeService runtimeService = processEngine.getRuntimeService();runtimeService.startProcessInstanceByKey("myProcess");}
}

给出以下输出(此处缩短):

—输出= eth0链接encap:以太网
inet地址:192.168.0.114广播:192.168.0.255掩码:255.255.255.0
… 流程以退出代码1完成

它输出有关我所有网络接口的信息,然后关闭整个JVM。 是的。 太恐怖了

尝试纳斯霍恩

第一个问题的解决方案是,我们需要将要在脚本中公开的内容列入白名单,并且默认情况下将所有内容都列入黑名单。 这样,用户将无法运行任何可以做恶意事情的类或方法。

在Activiti中,当javascript脚本任务是流程定义的一部分时,我们使用JDK中的ScriptEngine类将此脚本提供给JDK中嵌入的javascript引擎。 在JDK 6/7中是Rhino,在JDK 8中是Nashorn。 首先,我进行了一些认真的搜索,以找到Nashorn的解决方案(因为这将更加适用于未来)。 Nashorn确实具有“类过滤器”概念,可以有效地实施白名单。 但是,ScriptEngine抽象没有任何工具可以实际调整或配置Nashorn引擎。 我们必须做一些底层的魔术才能使其正常工作。

代替使用默认的Nashorn脚本引擎,我们自己在“ SecureScriptTask”(这是常规的JavaDelegate)中实例化Nashorn脚本引擎。 注意使用jdk.nashorn。*包的用法–不太好。 我们遵循https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/api.html中的文档,通过在Nashorn引擎中添加“ ClassFilter”来使脚本执行更加安全。 这实际上是可以在脚本中使用的已批准类的白名单。

public class SafeScriptTaskDemo2 implements JavaDelegate {private Expression script;public void execute(DelegateExecution execution) throws Exception {NashornScriptEngineFactory factory = new NashornScriptEngineFactory();ScriptEngine scriptEngine = factory.getScriptEngine(new SafeClassFilter());ScriptingEngines scriptingEngines = Context.getProcessEngineConfiguration().getScriptingEngines();Bindings bindings = scriptingEngines.getScriptBindingsFactory().createBindings(execution, false);scriptEngine.eval((String) script.getValue(execution), bindings);System.out.println("Java delegate done");}public static class SafeClassFilter implements ClassFilter {public boolean exposeToScripts(String s) {return false;}}}

当执行时,上面的脚本将不会执行,将引发一个异常,指出“线程“主”中的异常java.lang.RuntimeException:java.lang.ClassNotFoundException:java.lang.System.out.println”。

请注意,ClassFilter仅可从JDK 1.8.0_40使用(相当早!)。

但是,这不能解决无限循环的第二个问题。 让我们执行一个简单的脚本:

while (true) {print("Hello");
}

您可以猜测会做什么。 这将永远运行。 如果幸运的话,脚本任务在事务中执行时,事务超时将发生。 但这远不是一个体面的解决方案,因为它浪费了一段时间的CPU资源而无所作为。

使用大量内存的第三个问题也很容易证明:

var array = []
for(var i = 0; i < 2147483647; ++i) {array.push(i);java.lang.System.out.println(array.length);
}

启动流程实例时,内存将快速填满(仅以几个MB开头):

并最终以OutOfMemoryException: 线程“ main”中的异常java.lang.OutOfMemoryError:超出了GC开销限制

切换到犀牛

在下面的示例与上一个示例之间,花费了大量时间使Nashorn以某种方式拦截或应对无限循环/内存使用情况。 但是,经过大量搜索和试验后,似乎这些功能在Nashorn中还不是(还?)。 快速搜索将告诉您,我们不是唯一寻求解决方案的人。 通常会提到Rhino确实具有解决此问题的功能。

例如,在JDK <8中,Rhino javascript引擎具有“ instructionCount”回调机制,而Nashorn中不存在该回调机制。 它基本上为您提供了一种在回叫中执行逻辑的方法,该回叫会自动被每x条指令( 字节码指令!)调用。 我首先尝试(并且浪费了很多时间)用Nashorn模仿指令计数的想法,例如,首先美化脚本(因为人们可以将整个脚本写在一行上),然后在脚本中注入一行代码来触发回调。 但是,那是1)做起来不是很简单2)一个人仍然可以在无限运行/使用大量内存的一行上写一条指令。

搜索被困在那里,导致我们找到了Mozilla的Rhino引擎 。 自从很久以前将其包含在JDK中以来,它实际上就已经进一步发展了,而JDK中的版本并未进行这些更改! 阅读了(非常稀疏的)Rhino文档后,很明显Rhino在我们的用例方面似乎具有更丰富的功能。

Nashorn的ClassFilter与Rhino中的“ ClassShutter”概念匹配。 使用Rhino的回调机制解决了cpu和内存问题:您可以定义一个称为x指令的回调。 这意味着一行可能是数百个字节代码指令,并且每x条指令我们都会得到一个回调……。 在执行脚本时,它非常适合监视我们的cpu和内存使用情况。

如果您对我们在代码中实现这些想法感兴趣, 请在此处查看 。

这确实意味着无论您使用什么JDK版本,都不会使用嵌入式javascript引擎,而会一直使用Rhino。

尝试一下

要使用新的安全脚本功能,请添加以下依赖关系:

<dependency><groupId>org.activiti</groupId><artifactId>activiti-secure-javascript</artifactId><version>5.21.0</version>
</dependency>

这将暂时包括Rhino引擎。 这还将启用SecureJavascriptConfigurator ,在创建流程引擎之前需要对其进行配置:

SecureJavascriptConfigurator configurator = new SecureJavascriptConfigurator().setWhiteListedClasses(new HashSet<String>(Arrays.asList("java.util.ArrayList"))).setMaxStackDepth(10).setMaxScriptExecutionTime(3000L).setMaxMemoryUsed(3145728L).setNrOfInstructionsBeforeStateCheckCallback(10);ProcessEngine processEngine = new StandaloneInMemProcessEngineConfiguration().addConfigurator(configurator).buildProcessEngine();

这会将安全脚本配置为

  • 每10条指令,检查一次CPU执行时间和内存使用情况
  • 给脚本3秒3MB的执行时间
  • 将堆栈深度限制为10(以避免递归)
  • 将数组列表公开为可以在脚本中安全使用的类

从上方运行试图读取ifconfig并关闭JVM的脚本会导致:

TypeError:无法在对象[JavaPackage java.lang.Runtime]中调用属性getRuntime。 它不是功能,而是“对象”。

从上面运行无限循环脚本可以得出

线程“ main” java.lang.Error中的异常:最大variableScope时间超过了3000 ms

从上面运行内存使用脚本可以

线程“主” java.lang.Error中的异常:内存限制达到3145728字节

和欢呼! 解决了上面定义的问题

性能

我做了一个非常不科学的快速检查……我几乎不敢分享它,因为结果违背了我的设想。

我创建了一个快速主程序,该主程序运行带有脚本任务的流程实例10000次:

public class PerformanceUnsecure {public static void main (String[] args) {ProcessEngine processEngine = new StandaloneInMemProcessEngineConfiguration().buildProcessEngine();RepositoryService repositoryService = processEngine.getRepositoryService();repositoryService.createDeployment().addClasspathResource("performance.bpmn20.xml").deploy();Random random = new Random();RuntimeService runtimeService = processEngine.getRuntimeService();int nrOfRuns = 10000;long total = 0;for (int i=0; i<nrOfRuns; i++) {Map<String, Object> variables = new HashMap<String, Object>();variables.put("a", random.nextInt());variables.put("b", random.nextInt());long start = System.currentTimeMillis();runtimeService.startProcessInstanceByKey("myProcess", variables);long end = System.currentTimeMillis();total += (end - start);}System.out.println("Finished process instances : " + processEngine.getHistoryService().createHistoricProcessInstanceQuery().count());System.out.println("Total time = " + total + " ms");System.out.println("Avg time/process instance = " + ((double)total/(double)nrOfRuns) + " ms");}}

流程定义只是一个开始->脚本任务->结束。 脚本任务只是将变量添加到变量中,然后将结果保存在第三个变量中。

<scriptTask id="myScriptTask" scriptFormat="javascript"><script>var c = a + b;execution.setVariable('c', c);</script>
</scriptTask>

我运行了五次,平均每个流程实例为2.57毫秒。 这是在最近的JDK 8(所以是Nashorn)上。

然后,我切换了上面的前两行以使用新的安全脚本,从而切换到Rhino并启用了安全功能:

SecureJavascriptConfigurator configurator = new SecureJavascriptConfigurator().addWhiteListedClass("org.activiti.engine.impl.persistence.entity.ExecutionEntity").setMaxStackDepth(10).setMaxScriptExecutionTime(3000L).setMaxMemoryUsed(3145728L).setNrOfInstructionsBeforeStateCheckCallback(1);ProcessEngine processEngine = new StandaloneInMemProcessEngineConfiguration().addConfigurator(configurator).buildProcessEngine();

再次进行了五次运行……并获得了1.07毫秒/流程实例。 这是同一件事的两倍以上

当然,这不是一个真正的考验。 我以类白名单检查和回调为前提,假设Rhino的执行速度会变慢,但是没有这种事情。 也许这种特殊情况更适合Rhino……如果有人可以解释,请发表评论。 但这仍然是一个有趣的结果。

结论

如果您在流程定义中使用脚本,请仔细阅读引擎中的此新安全脚本功能。 由于这是一项新功能,非常欢迎反馈和改进!

翻译自: https://www.javacodegeeks.com/2016/06/secure-scripting-activiti-works.html

Activiti中的安全脚本如何工作相关推荐

  1. Activiti中的高级脚本:自定义配置注入

    脚本任务可能是Activiti代码库中"最古老的"类之一,但我认为它仍然未被许多人使用. (可以理解的?)缺点当然是性能(解释还是编译),并且从IDE角度来看支持较少. 但是,好处 ...

  2. activiti脚本任务_Activiti中的安全脚本如何工作

    activiti脚本任务 最近的Activiti 5.21.0版本的突出特点之一是"安全脚本". Activiti用户指南中详细介绍了启用和使用此功能的方法 . 在这篇文章中,我将 ...

  3. activiti脚本任务_Activiti中的高级脚本:自定义配置注入

    activiti脚本任务 脚本任务可能是Activiti代码库中"最古老的"类之一,但是我认为它仍然未被许多人使用. (感知到的)缺点当然是性能(解释还是编译),并且从IDE角度来 ...

  4. Blender中的Python脚本介绍学习教程

    Blender中的Python脚本介绍学习教程 MP4 |视频:h264,1280×720 |音频:AAC,48000 Hz 语言:英语+中英文字幕(根据原英文字幕机译更准确)|大小解压后:1.63 ...

  5. Linux中执行shell脚本的4种方法

    这篇文章主要介绍了Linux中执行shell脚本的4种方法总结,即在Linux中运行shell脚本的4种方法,需要的朋友可以参考下. bash shell 脚本的方法有多种,现在作个小结.假设我们编写 ...

  6. 如何在html中写javascript代码,如何在本地html代码中使用javascript脚本

    我在jsfiddle中的代码正在工作,但是当我尝试在本地使用它时没有. 我不知道如何解决它,因为我认为代码是正确的. 我也尝试下载Jquery文件,并将其链接到代码中,但它不起作用.我确信这是一个愚蠢 ...

  7. excel调用python编程-如何在excel中调用python脚本

    如何在excel中调用python脚本 发布时间:2020-07-03 14:15:28 来源:亿速云 阅读:155 如何在excel中调用python脚本?针对这个问题,这篇文章详细介绍了相对应的分 ...

  8. power python_在 Power BI Desktop 中运行 Python 脚本

    在 Power BI Desktop 中运行 Python 脚本Run Python scripts in Power BI Desktop 06/02/2020 本文内容 你可以直接在 Power ...

  9. Linux 中启用 Shell 脚本的调试模式

    shell 脚本调试系列 Linux 中启用 Shell 脚本的调试模式 在 Shell 脚本中执行语法检查调试模式 在 Shell 脚本中跟踪调试命令的执行 概述 脚本是存储在一个文件的一系列命令. ...

最新文章

  1. 几个SQL命令的使用
  2. tcm可信密码模块linux,基于可信密码模块的可信电子签名终端设计与实现
  3. java finalize逃脱_关于Java中的finalize()方法
  4. 鸿蒙系统系列教程3-鸿蒙OS的技术特征讲解
  5. 07-01-安装-Exchange Server 2019 on Win 2019 Core
  6. 华东政法大学教学管理系统_中国科学技术大学1022万采购3包生命科学仪器
  7. Python 18.4 aiohttp
  8. 将JQGrid与Spring MVC和Gson集成
  9. if else的使用以及如何从键盘获取数值
  10. python如何导入numpy简书_Webpack 之常用配置(一)
  11. get post请求区别_网页常见的两种请求方式Get和Post
  12. Linux常用命令,Linux常用基本命令大全
  13. MATLAB的图像显示函数imshow()详解
  14. excel内容少却文件很大_(excel内容很少,文件却很大,怎么解决)excel表格数据少但是过大怎么办...
  15. 最好的跑步耳机推荐、盘点五款公认最好的跑步耳机
  16. 设置BIOS从USB启动!
  17. 分享一个非常好的壁纸网站http://www.itoobz.com
  18. 英雄榜拍卖行服务器维护,随时逛拍卖行 《魔兽世界》手机英雄榜登陆国内
  19. GitHub 版本控制 项目托管 04 创建GitHub远程仓库-阿里云开发者社区
  20. DSP与STM32的对比浅见(二)

热门文章

  1. Vue.js2.0开发环境搭建(一)
  2. 小菜:Java异常处理的10个最佳实践
  3. Java码农必须掌握的循环删除List元素的正确方法
  4. Ajax基本案例详解之$.get的实现
  5. Sentinel控制台
  6. php 正则获取html标签,php正则取嵌套html标签
  7. bmp180气压传感器工作原理_陕西压力传感器的工作原理信息推荐
  8. 在 eclipse 中 设置 jvm 的 运行时目录
  9. 让CentOS能用yum自动安装rar和unrar
  10. 2021年最新springcloud配置中心不生效的版本原因