Java 保护 Nashorn JS 执行

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20793089/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 04:21:54  来源:igfitidea点击:

Secure Nashorn JS Execution

javajavascriptnashorn

提问by tom_ma

How can I securely execute some user supplied JS code using Java8 Nashorn?

如何使用 Java8 Nashorn 安全地执行一些用户提供的 JS 代码?

The script extends some computations for some servlet based reports. The app has many different (untrusted) users. The scripts should only be able to access a Java Object and those returned by the defined members. By default the scripts could instantiate any class using Class.forName() (using .getClass() of my supplied object). Is there any way to prohibit access to any java class not explicitly specified by me?

该脚本为一些基于 servlet 的报告扩展了一些计算。该应用程序有许多不同的(不受信任的)用户。脚本应该只能访问 Java 对象和那些由定义的成员返回的对象。默认情况下,脚本可以使用 Class.forName() (使用我提供的对象的 .getClass() )实例化任何类。有什么方法可以禁止访问我未明确指定的任何 Java 类?

回答by Amadan

I'd say overriding the supplied class's classloader is easiest way to control access to classes.

我会说覆盖提供的类的类加载器是控制对类的访问的最简单方法。

(Disclaimer: I'm not really familiar with newer Java, so this answer may be old-school/obsolete)

(免责声明:我不太熟悉较新的 Java,所以这个答案可能是过时的/过时的)

回答by Tomas

I've researched ways of allowing users to write a simple script in a sandbox that is allowed access to some basic objects provided by my application (in the same way Google Apps Scriptworks). My conclusion was that this is easier/better documented with Rhino than with Nashorn. You can:

我研究了允许用户在沙箱中编写简单脚本的方法,该脚本允许访问我的应用程序提供的一些基本对象(与Google Apps 脚本的工作方式相同)。我的结论是,使用 Rhino 比使用 Nashorn 更容易/更好地记录。你可以:

  1. Define a class-shutter to avoid access to other classes: http://codeutopia.net/blog/2009/01/02/sandboxing-rhino-in-java/

  2. Limit the number of instructions to avoid endess-loops with observeInstructionCount: http://www-archive.mozilla.org/rhino/apidocs/org/mozilla/javascript/ContextFactory.html

  1. 定义类快门以避免访问其他类:http: //codeutopia.net/blog/2009/01/02/sandboxing-rhino-in-java/

  2. 使用observeInstructionCount限制指令数量以避免endess-loops:http://www-archive.mozilla.org/rhino/apidocs/org/mozilla/javascript/ContextFactory.html

However be warned that with untrusted users this is not enough, because they can still (by accident or on purpose) allocate a hugh amount of memory, causing your JVM to throw an OutOfMemoryError. I have not found a safe solution to this last point yet.

但是请注意,对于不受信任的用户,这还不够,因为他们仍然可以(偶然或故意)分配大量内存,从而导致您的 JVM 抛出 OutOfMemoryError。对于最后一点,我还没有找到安全的解决方案。

回答by Edu Garcia

An external sandbox library can be used if you don't want to implement your own ClassLoader & SecurityManager (that's the only way of sandboxing for now).

如果您不想实现自己的 ClassLoader 和 SecurityManager(这是目前唯一的沙箱方式),可以使用外部沙箱库。

I've tried "The Java Sandbox" (http://blog.datenwerke.net/p/the-java-sandbox.html) although it's a bit rough around the edges, but it works.

我已经尝试过“Java 沙盒”(http://blog.datenwerke.net/p/the-java-sandbox.html),虽然它的边缘有点粗糙,但它确实有效。

回答by Kong

I asked this question on the Nashorn mailing lista while back:

不久前,在 Nashorn 邮件列表上问了这个问题

Are there any recommendations for the best way to restrict the classes that Nashorn scripts can create to a whitelist? Or is the approach the same as any JSR223 engine (custom classloader on the ScriptEngineManager constructor)?

对于将 Nashorn 脚本可以创建的类限制到白名单的最佳方法,是否有任何建议?或者该方法是否与任何 JSR223 引擎(ScriptEngineManager 构造函数上的自定义类加载器)相同?

And got this answer from one of the Nashorn devs:

从一位 Nashorn 开发人员那里得到了这个答案:

Hi,

  • Nashorn already filters classes - only public classes of non-sensitive packages (packages listed in package.access security property aka 'sensitive'). Package access check is done from a no-permissions context. i.e., whatever package that can be accessed from a no-permissions class are only allowed.

  • Nashorn filters Java reflective and jsr292 access - unless script has RuntimePermission("nashorn.JavaReflection"), the script wont be able to do reflection.

  • The above two require running with SecurityManager enabled. Under no security manager, the above filtering won't apply.

  • You could remove global Java.type function and Packages object (+ com,edu,java,javafx,javax,org,JavaImporter) in global scope and/or replace those with whatever filtering functions that you implement. Because, these are the only entry points to Java access from script, customizing these functions => filtering Java access from scripts.

  • There is an undocumented option (right now used only to run test262 tests) "--no-java" of nashorn shell that does the above for you. i.e., Nashorn won't initialize Java hooks in global scope.

  • JSR223 does not provide any standards based hook to pass a custom class loader. This may have to be addressed in a (possible) future update of jsr223.

Hope this helps,

-Sundar

你好,

  • 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”为您执行上述操作。即,Nashorn 不会在全局范围内初始化 Java 钩子。

  • JSR223 不提供任何基于标准的钩子来传递自定义类加载器。这可能必须在 jsr223 的(可能的)未来更新中解决。

希望这可以帮助,

-桑达尔

回答by ccleve

So far as I can tell, you can't sandbox Nashorn. An untrusted user can execute the "Additional Nashorn Built-In Functions" listed here:

据我所知,你不能对 Nashorn 进行沙箱处理。不受信任的用户可以执行此处列出的“其他 Nashorn 内置函数”:

https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/shell.html

https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/shell.html

which include "quit()". I tested it; it exits the JVM entirely.

其中包括“退出()”。我测试过了;它完全退出JVM。

(As an aside, in my setup the global objects, $ENV, $ARG, did not work, which is good.)

(顺便说一句,在我的设置中,全局对象 $ENV、$ARG 不起作用,这很好。)

If I'm wrong about this, someone please leave a comment.

如果我对此有误,请有人发表评论。

回答by mxro

You can quite easily create a ClassFilterwhich allows fine-grained control of which Java classes are available in JavaScript.

您可以很容易地创建一个ClassFilter允许对 JavaScript 中可用的 Java 类进行细粒度控制的 。

Following the example from the Oracle Nashorn Docs:

按照Oracle Nashorn Docs 中的示例:

class MyCF implements ClassFilter {
    @Override
    public boolean exposeToScripts(String s) {
      if (s.compareTo("java.io.File") == 0) return false;
      return true;
    }
}

I have wrapped this an a few other measures in a small library today: Nashorn Sandbox(on GitHub). Enjoy!

我今天在一个小型库中包含了其他一些措施:Nashorn Sandbox(在 GitHub 上)。享受!

回答by mkobit

Added in 1.8u40, you can use the ClassFilterto restrict what classes the engine can use.

1.8u40 中添加,您可以使用ClassFilter来限制引擎可以使用的类。

Here is an example from the Oracle documentation:

这是Oracle 文档中的一个示例:

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();
  }
}

This example prints the following:

C:\Java\jre8
Create file variable
Exception caught: java.lang.RuntimeException: java.lang.ClassNotFoundException:
java.io.File
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

回答by Damiano Falcioni

The best way to secure a JS execution in Nashorn is to enable the SecurityManager and let Nashorn deny the critical operations. In addition you can create a monitoring class that check the script execution time and memory in order to avoid infinite loops and outOfMemory. In case you run it in a restricted environment without possibility to setup the SecurityManager, you can think to use the Nashorn ClassFilter to deny all/partial access to the Java classes. In addition to that you must overwrite all the critical JS functions (like quit() etc.). Have a look at this function that manage all this aspects (except memory management):

在 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);
    }
}

The function currently use the deprecated Thread stop(). An improvement can be execute the JS not in a Thread but in a separate Process.

该函数当前使用已弃用的 Thread stop()。一个改进可以是在一个单独的进程中而不是在一个线程中执行 JS。

PS: here Nashorn is loaded through reflexion but the equivalent Java code is also provided in the comments

PS:这里 Nashorn 是通过反射加载的,但注释中也提供了等效的 Java 代码

回答by zezuha

Without the use of Security Manager it is not possible to securely execute JavaScript on Nashorn.

如果不使用安全管理器,就不可能在 Nashorn 上安全地执行 JavaScript。

In all releases of Oracle Hotspot that included Nashorn one can write JavaScript that will execute any Java/JavaScript code on this JVM. As of January 2019, Oracle Security Team insist that use of Security Manager is mandatory.

在包含 Nashorn 的所有 Oracle Hotspot 版本中,可以编写 JavaScript 来在此 JVM 上执行任何 Java/JavaScript 代码。截至 2019 年 1 月,Oracle 安全团队坚持必须使用安全管理器。

One of the problems is already discussed in https://github.com/javadelight/delight-nashorn-sandbox/issues/73

https://github.com/javadelight/delight-nashorn-sandbox/issues/73 中已经讨论了其中一个问题