如何使用Java8 Nashorn安全地执行一些用户提供的JS代码?
该脚本扩展了一些基于servlet的报告的计算。该应用程序有许多不同(不受信任)的用户。脚本应该只能访问Java对象和已定义成员返回的Java对象。默认情况下,脚本可以使用Class.forName()(使用我提供的对象的.getClass())实例化任何类。有没有办法禁止访问我没有明确指定的任何java类?
答案 0 :(得分:25)
我asked this question on the Nashorn mailing list一会儿回来:
对于最好的方法,是否有任何建议 将Nashorn脚本可以创建的类限制为白名单? 或者方法与任何JSR223引擎(自定义类加载器)相同 在ScriptEngineManager构造函数上)?
从Nashorn开发者那里得到了这个答案:
您好,
Nashorn已经过滤了类 - 只有非敏感包的公共类(package.access security中列出的包) 财产又名'敏感')。包访问检查是从a 无权限上下文。即,可以访问的任何包 只允许来自无权限类。
Nashorn过滤Java反射和jsr292访问 - 除非脚本有RuntimePermission(“nashorn.JavaReflection”),脚本不会 能够做反思。
以上两项要求在启用SecurityManager的情况下运行。在没有安全管理员的情况下,上述过滤将不适用。
您可以在全局范围内删除全局Java.type函数和Packages对象(+ com,edu,java,javafx,javax,org,JavaImporter)和/或 用你实现的任何过滤功能替换那些。 因为,这些是从脚本访问Java的唯一入口点, 自定义这些函数=>从脚本过滤Java访问。
有一个未记录的选项(现在只用于运行test262测试)nashorn shell的“--no-java”为您执行上述操作。即, Nashorn不会在全局范围内初始化Java钩子。
JSR223没有提供任何基于标准的钩子来传递自定义类加载器。这可能必须在(可能的)未来解决 更新jsr223。
希望这有帮助,
-Sundar
答案 1 :(得分:17)
在 1.8u40 中添加,您可以使用ClassFilter
来限制引擎可以使用的类。
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
答案 2 :(得分:8)
我研究了允许用户在沙箱中编写一个简单脚本的方法,该沙箱允许访问我的应用程序提供的一些基本对象(以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。我还没有找到最后一点的安全解决方案。
答案 3 :(得分:6)
据我所知,你不能沙箱Nashorn。不受信任的用户可以执行此处列出的“Additional Nashorn Built-In Functions”:
https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/shell.html
包括“quit()”。我测试了它;它完全退出JVM。
(顺便说一句,在我的设置中,全局对象$ ENV,$ ARG不起作用,这很好。)
如果我对此有误,请有人留言。
答案 4 :(得分:5)
您可以非常轻松地创建ClassFilter
,它允许对JavaScript中可用的Java类进行细粒度控制。
class MyCF implements ClassFilter {
@Override
public boolean exposeToScripts(String s) {
if (s.compareTo("java.io.File") == 0) return false;
return true;
}
}
我今天在一个小型图书馆中包含了一些其他措施:Nashorn Sandbox(在GitHub上)。享受!
答案 5 :(得分:2)
在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,但评论中也提供了等效的Java代码
答案 6 :(得分:0)
我会说覆盖提供的类的类加载器是控制类访问的最简单方法。
(免责声明:我对新Java并不熟悉,所以这个答案可能是老派/过时的)
答案 7 :(得分:0)
如果您不想实现自己的ClassLoader&amp;可以使用外部沙箱库。 SecurityManager(现在是沙盒的唯一方法)。
我已经尝试过#Java; Java Sandbox&#34; (http://blog.datenwerke.net/p/the-java-sandbox.html)尽管它的边缘有点粗糙,但它有效。
答案 8 :(得分:0)
如果不使用Security Manager,就无法在Nashorn上安全地执行JavaScript。
在包括Nashorn在内的所有Oracle Hotspot版本中,都可以编写JavaScript,该JavaScript将在此JVM上执行任何Java / JavaScript代码。 从2019年1月起,Oracle安全团队坚持必须强制使用Security Manager。
其中一个问题已经在https://github.com/javadelight/delight-nashorn-sandbox/issues/73
中进行了讨论