安全的 Nashorn JS 执行
Posted
技术标签:
【中文标题】安全的 Nashorn JS 执行【英文标题】:Secure Nashorn JS Execution 【发布时间】:2014-01-14 14:19:22 【问题描述】:如何使用 Java8 Nashorn 安全地执行一些用户提供的 JS 代码?
脚本为一些基于 servlet 的报告扩展了一些计算。该应用程序有许多不同的(不受信任的)用户。脚本应该只能访问 Java 对象以及由定义的成员返回的对象。默认情况下,脚本可以使用 Class.forName() 实例化任何类(使用我提供的对象的 .getClass() )。有什么方法可以禁止访问我没有明确指定的任何 java 类?
【问题讨论】:
这是一个非常棒的问题,而且会被越来越多的人问到。我希望有人将所有事实/数据/代码/样本/答案/等收集到博客或其他东西中。除了在 Java 中对 JS 代码进行沙箱处理之外,还有更高级的主题,例如如何防止某人运行无休止的 JS 循环来破坏执行。也就是说,如何在正在执行的第三方 JS 中插入执行看门狗。无论如何,谢谢你的问题! 你也应该看看这个:***.com/a/48259901/1035398 【参考方案1】:我不久前asked this question on the Nashorn mailing list:
有没有什么最好的方法推荐 将 Nashorn 脚本可以创建的类限制为白名单? 或者该方法是否与任何 JSR223 引擎相同(自定义类加载器 在 ScriptEngineManager 构造函数上)?
并从一位 Nashorn 开发人员那里得到了这个答案:
嗨,
Nashorn 已经过滤了类 - 仅非敏感包的公共类(package.access 安全中列出的包) 属性又名“敏感”)。包访问检查是从 无权限上下文。即,可以访问的任何包 只允许来自无权限类。
Nashorn 过滤 Java 反射和 jsr292 访问 - 除非脚本具有 RuntimePermission("nashorn.JavaReflection"),否则脚本不会 能够进行反思。
以上两个需要在启用 SecurityManager 的情况下运行。在没有安全管理员的情况下,上述过滤将不适用。
您可以在全局范围内删除全局 Java.type 函数和 Packages 对象(+ com、edu、java、javafx、javax、org、JavaImporter)和/或 用您实现的任何过滤功能替换它们。 因为,这些是从脚本访问 Java 的唯一入口点, 自定义这些函数 => 过滤来自脚本的 Java 访问。
nashorn shell 有一个未记录的选项(现在仅用于运行 test262 测试)“--no-java”,可以为您执行上述操作。 IE。, Nashorn 不会在全局范围内初始化 Java 挂钩。
JSR223 不提供任何基于标准的挂钩来传递自定义类加载器。这可能必须在(可能的)未来解决 jsr223的更新。
希望这会有所帮助,
-桑达尔
【讨论】:
您可以将--no-java
(和其他选项)通过以下方式传递给引擎:final ScriptEngine engine = new NashornScriptEngineFactory().getScriptEngine(new String[] "--no-java" );
谢谢,但你能分享一下你对这个开关的参考吗?
我只是想评论一下,只要不删除 loadWithNewGlobal,第四种方法就行不通。例如,我可以使用以下代码重新创建 Java 对象: loadWithNewGlobal(script: "arguments[0].Java = Java", name: "exploit", this)
@gsimard - 我也想知道,然后找到了hg.openjdk.java.net/jdk8/jdk8/nashorn/rev/eb7b8340ce3a - 由***.com/a/24468398/751158 提供。另见:wiki.openjdk.java.net/display/Nashorn/Nashorn+extensions。【参考方案2】:
在1.8u40中添加,您可以使用ClassFilter
来限制引擎可以使用的类。
这是来自Oracle documentation 的示例:
import javax.script.ScriptEngine; import jdk.nashorn.api.scripting.ClassFilter; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; public class MyClassFilterTest class MyCF implements ClassFilter @Override public boolean exposeToScripts(String s) if (s.compareTo("java.io.File") == 0) return false; return true; public void testClassFilter() final String script = "print(java.lang.System.getProperty(\"java.home\"));" + "print(\"Create file variable\");" + "var File = Java.type(\"java.io.File\");"; NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); ScriptEngine engine = factory.getScriptEngine( new MyClassFilterTest.MyCF()); try engine.eval(script); catch (Exception e) System.out.println("Exception caught: " + e.toString()); public static void main(String[] args) MyClassFilterTest myApp = new MyClassFilterTest(); myApp.testClassFilter();
此示例打印以下内容:
C:\Java\jre8 Create file variable Exception caught: java.lang.RuntimeException: java.lang.ClassNotFoundException: java.io.File
【讨论】:
【参考方案3】:我研究了允许用户在沙箱中编写简单脚本的方法,该脚本允许访问我的应用程序提供的一些基本对象(与Google Apps Script 的工作方式相同)。我的结论是,使用 Rhino 比使用 Nashorn 更容易/更好地记录这一点。你可以:
定义一个类快门以避免访问其他类:http://codeutopia.net/blog/2009/01/02/sandboxing-rhino-in-java/
使用 observeInstructionCount 限制指令数量以避免结束循环:http://www-archive.mozilla.org/rhino/apidocs/org/mozilla/javascript/ContextFactory.html
但是请注意,对于不受信任的用户,这还不够,因为他们仍然可以(意外或故意)分配大量内存,从而导致您的 JVM 抛出 OutOfMemoryError。对于最后一点,我还没有找到安全的解决方案。
【讨论】:
问题是如何保护 Nashorn,而不是 Rhino 这就是为什么我说“我的结论是,使用 Rhino 比使用 Nashorn 更容易/更好地记录这一点。”两者都实现了相似的目标,而且 Rhino 更容易锁定,所以 @tom_ma 使用 Rhino 可能会更好。 Rhino 和 Nashorn 都执行 JS。这就是相似之处的结束! 我目前有相同的问题来锁定 JS 执行,并且使用 Rhino 是唯一有效的解决方案(运行 Java 6)。正如答案中所说,“observeInstructionCount”并非在所有情况下都有效:它可以防止无限循环,但不能检测无限递归(最终会出现 OutOfMemory)。使链进入递归的唯一方法是限制 StackSize,您可以通过 c.ontext.setMaximumInterpreterStackDepth(500) 来实现。【参考方案4】:您可以很容易地创建一个ClassFilter
,它允许对 JavaScript 中可用的 Java 类进行细粒度控制。
按照Oracle Nashorn Docs中的示例:
class MyCF implements ClassFilter
@Override
public boolean exposeToScripts(String s)
if (s.compareTo("java.io.File") == 0) return false;
return true;
我今天在一个小型库中包含了其他一些措施:Nashorn Sandbox(在 GitHub 上)。享受吧!
【讨论】:
哇,我应该先看看你的帖子,然后再评论上面的 while-loops 限制。我在 GitHub 网站上阅读了该项目的主页,看起来很有希望。如果它们运行良好,那么 CPU 限制之类的东西就会令人惊叹。我肯定会阅读代码并肯定会对其进行测试。非常感谢您的帖子! 我需要制作一个可以解析的 Javascript 解析器:数学、布尔查询和按位操作,但几乎没有其他内容。是否可以以这种方式限制评估?在exposeToScripts中我需要允许什么想法? @perry-monschau :查看上面链接的 Nashorn Sandbox GitHub 项目。那里的默认设置只允许基本的 JavaScript 功能。但是,它超出了您的要求:例如,字符串操作将起作用,而且我不知道有任何方法可以在任何 JS 解析器中防止这种情况发生。希望这会有所帮助。Class.forName
?你怎么能阻止它?
@TheRealChx101 Class.forName 在有 ClassFilter 时将始终被阻止。沙盒还阻止对 Java.type('') 的访问。查看一些示例here【参考方案5】:
据我所知,您无法对 Nashorn 进行沙盒处理。不受信任的用户可以执行此处列出的“其他 Nashorn 内置函数”:
https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/shell.html
其中包括“quit()”。我测试了它;它完全退出 JVM。
(顺便说一句,在我的设置中,全局对象 $ENV、$ARG 不起作用,这很好。)
如果我错了,请发表评论。
【讨论】:
它说“您可以使用带有 -scripting 选项的 jjs 命令在 Nashorn 中启用 shell 脚本扩展”。那么如果只是不启用呢?而且,总的来说,这篇文章似乎是关于一些独立的命令行工具。 大多数列出的函数也可以在没有脚本扩展的情况下使用,这包括quit
。但似乎只需在用户提供的脚本前加上如下前导符就可以解决问题:var quit=function()throw 'Unsupported operation: quit';;var exit=function()throw 'Unsupported operation: exit';;
我还要禁用print
和大多数其他全局函数,因为它们也可能具有破坏性或导致其他安全问题(例如,load
可用于测试服务器上的文件是否存在)。【参考方案6】:
在 Nashorn 中保护 JS 执行的最佳方法是启用 SecurityManager 并让 Nashorn 拒绝关键操作。 此外,您可以创建一个监控类来检查脚本执行时间和内存,以避免无限循环和 outOfMemory。 如果您在受限环境中运行它而无法设置 SecurityManager,您可以考虑使用 Nashorn ClassFilter 拒绝对 Java 类的所有/部分访问。除此之外,您必须覆盖所有关键的 JS 函数(如 quit() 等)。 看看这个管理所有这些方面的函数(内存管理除外):
public static Object javascriptSafeEval(HashMap<String, Object> parameters, String algorithm, boolean enableSecurityManager, boolean disableCriticalJSFunctions, boolean disableLoadJSFunctions, boolean defaultDenyJavaClasses, List<String> javaClassesExceptionList, int maxAllowedExecTimeInSeconds) throws Exception
System.setProperty("java.net.useSystemProxies", "true");
Policy originalPolicy = null;
if(enableSecurityManager)
ProtectionDomain currentProtectionDomain = this.getClass().getProtectionDomain();
originalPolicy = Policy.getPolicy();
final Policy orinalPolicyFinal = originalPolicy;
Policy.setPolicy(new Policy()
@Override
public boolean implies(ProtectionDomain domain, Permission permission)
if(domain.equals(currentProtectionDomain))
return true;
return orinalPolicyFinal.implies(domain, permission);
);
try
SecurityManager originalSecurityManager = null;
if(enableSecurityManager)
originalSecurityManager = System.getSecurityManager();
System.setSecurityManager(new SecurityManager()
//allow only the opening of a socket connection (required by the JS function load())
@Override
public void checkConnect(String host, int port, Object context)
@Override
public void checkConnect(String host, int port)
);
try
ScriptEngine engineReflex = null;
try
Class<?> nashornScriptEngineFactoryClass = Class.forName("jdk.nashorn.api.scripting.NashornScriptEngineFactory");
Class<?> classFilterClass = Class.forName("jdk.nashorn.api.scripting.ClassFilter");
engineReflex = (ScriptEngine)nashornScriptEngineFactoryClass.getDeclaredMethod("getScriptEngine", new Class[]Class.forName("jdk.nashorn.api.scripting.ClassFilter")).invoke(nashornScriptEngineFactoryClass.newInstance(), Proxy.newProxyInstance(classFilterClass.getClassLoader(), new Class[]classFilterClass, new InvocationHandler()
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
if(method.getName().equals("exposeToScripts"))
if(javaClassesExceptionList != null && javaClassesExceptionList.contains(args[0]))
return defaultDenyJavaClasses;
return !defaultDenyJavaClasses;
throw new RuntimeException("no method found");
));
/*
engine = new jdk.nashorn.api.scripting.NashornScriptEngineFactory().getScriptEngine(new jdk.nashorn.api.scripting.ClassFilter()
@Override
public boolean exposeToScripts(String arg0)
...
);
*/
catch(Exception ex)
throw new Exception("Impossible to initialize the Nashorn Engine: " + ex.getMessage());
final ScriptEngine engine = engineReflex;
if(parameters != null)
for(Entry<String, Object> entry : parameters.entrySet())
engine.put(entry.getKey(), entry.getValue());
if(disableCriticalJSFunctions)
engine.eval("quit=function()throw 'quit() not allowed';;exit=function()throw 'exit() not allowed';;print=function()throw 'print() not allowed';;echo=function()throw 'echo() not allowed';;readFully=function()throw 'readFully() not allowed';;readLine=function()throw 'readLine() not allowed';;$ARG=null;$ENV=null;$EXEC=null;$OPTIONS=null;$OUT=null;$ERR=null;$EXIT=null;");
if(disableLoadJSFunctions)
engine.eval("load=function()throw 'load() not allowed';;loadWithNewGlobal=function()throw 'loadWithNewGlobal() not allowed';;");
//nashorn-polyfill.js
engine.eval("var global=this;var window=this;var process=env:;var console=;console.debug=print;console.log=print;console.warn=print;console.error=print;");
class ScriptMonitor
public Object scriptResult = null;
private boolean stop = false;
Object lock = new Object();
@SuppressWarnings("deprecation")
public void startAndWait(Thread threadToMonitor, int secondsToWait)
threadToMonitor.start();
synchronized (lock)
if(!stop)
try
if(secondsToWait<1)
lock.wait();
else
lock.wait(1000*secondsToWait);
catch (InterruptedException e)
throw new RuntimeException(e);
if(!stop)
threadToMonitor.interrupt();
threadToMonitor.stop();
throw new RuntimeException("Javascript forced to termination: Execution time bigger then " + secondsToWait + " seconds");
public void stop()
synchronized (lock)
stop = true;
lock.notifyAll();
final ScriptMonitor scriptMonitor = new ScriptMonitor();
scriptMonitor.startAndWait(new Thread(new Runnable()
@Override
public void run()
try
scriptMonitor.scriptResult = engine.eval(algorithm);
catch (ScriptException e)
throw new RuntimeException(e);
finally
scriptMonitor.stop();
), maxAllowedExecTimeInSeconds);
Object ret = scriptMonitor.scriptResult;
return ret;
finally
if(enableSecurityManager)
System.setSecurityManager(originalSecurityManager);
finally
if(enableSecurityManager)
Policy.setPolicy(originalPolicy);
该函数当前使用已弃用的 Thread stop()。一个改进是可以在一个单独的进程中而不是在一个线程中执行 JS。
PS:这里 Nashorn 是通过反射加载的,但是 cmets 中也提供了等效的 Java 代码
【讨论】:
【参考方案7】:我想说覆盖提供的类的类加载器是控制对类的访问的最简单方法。
(免责声明:我对较新的 Java 不是很熟悉,所以这个答案可能是老派/过时的)
【讨论】:
【参考方案8】:如果您不想实现自己的 ClassLoader 和 SecurityManager(这是目前唯一的沙箱方式),可以使用外部沙箱库。
我已经尝试过“Java 沙盒”(http://blog.datenwerke.net/p/the-java-sandbox.html),虽然它的边缘有点粗糙,但它确实有效。
【讨论】:
【参考方案9】:如果不使用安全管理器,就不可能在 Nashorn 上安全地执行 JavaScript。
在包含 Nashorn 的所有 Oracle Hotspot 版本中,人们都可以编写 JavaScript 来在这个 JVM 上执行任何 Java/JavaScript 代码。 自 2019 年 1 月起,Oracle 安全团队坚持使用安全管理器是强制性的。
其中一个问题已经在https://github.com/javadelight/delight-nashorn-sandbox/issues/73讨论过
【讨论】:
以上是关于安全的 Nashorn JS 执行的主要内容,如果未能解决你的问题,请参考以下文章