Java8 Nashornを使用してユーザー提供のJSコードを安全に実行するにはどうすればよいですか?
このスクリプトは、一部のサーブレットベースのレポートの計算を拡張します。アプリには多くの異なる(信頼できない)ユーザーがいます。スクリプトは、Javaオブジェクトおよび定義されたメンバーによって返されるオブジェクトのみにアクセスできる必要があります。デフォルトでは、スクリプトはClass.forName()を使用して任意のクラスをインスタンス化できます(my。提供されたオブジェクト)Java私が明示的に指定していないクラスへのアクセスを禁止する方法はありますか?
私 Nashornメーリングリストでこの質問をしました しばらく前に:
Nashornスクリプトが作成できるクラスをホワイトリストに制限する最良の方法に関する推奨事項はありますか?または、このアプローチはJSR223エンジン(ScriptEngineManagerコンストラクターのカスタムクラスローダー)と同じですか?
そしてNashornの開発者の一人からこの答えを得ました:
こんにちは、
Nashornはすでにクラスをフィルターします-機密ではないパッケージ(package.accessセキュリティプロパティaka 'sensitive'にリストされているパッケージ)のパブリッククラスのみ。パッケージアクセスチェックは、権限のないコンテキストから行われます。つまり、権限のないクラスからアクセスできるパッケージはすべて許可されます。
NashornフィルターJavaリフレクティブおよびjsr292アクセス-スクリプトにRuntimePermission( "nashorn.JavaReflection")がない限り、スクリプトはリフレクションを実行できません。
上記の2つはSecurityManagerを有効にして実行する必要があります。セキュリティマネージャーがない場合、上記のフィルタリングは適用されません。
グローバルスコープのグローバルJava.type関数とPackagesオブジェクト(+ com、edu、Java、javafx、javax、org、JavaImporter)を削除したり、実装したフィルタリング関数でこれらを置き換えたりすることができます。 Javaスクリプトからのアクセス、これらの関数のカスタマイズ=>フィルタリングJavaスクリプトからのアクセス)への唯一のエントリポイントであるため、.
文書化されていないオプション(現時点ではtest262テストを実行するためにのみ使用されます)には、上記を行うnashorn Shellの「--no-Java」があります。つまり、NashornはグローバルスコープのJavaフックを初期化しません。
JSR223は、カスタムクラスローダーを渡すための標準ベースのフックを提供していません。これは、jsr223の(可能な)将来の更新で対処する必要がある場合があります。
お役に立てれば、
-サンダー
1.8u40で追加された ClassFilter
を使用して、エンジンが使用できるクラスを制限できます。
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(); } }
この例では、以下を出力します。
C:\Java\jre8 Create file variable Exception caught: Java.lang.RuntimeException: Java.lang.ClassNotFoundException: Java.io.File
私は、アプリケーションが提供するいくつかの基本的なオブジェクトへのアクセスを許可されたシンプルなスクリプトをユーザーがサンドボックスに書き込むことができるようにする方法を調査しました(同じように Google Apps Script が機能します)。私の結論は、これはNashornよりもRhinoで文書化する方が簡単/優れているということです。あなたはできる:
他のクラスへのアクセスを避けるためにクラスシャッターを定義します: http://codeutopia.net/blog/2009/01/02/sandboxing-rhino-in-Java/
ObserveInstructionCountでendess-loopsを回避するために命令の数を制限します: http://www-archive.mozilla.org/rhino/apidocs/org/mozilla/javascript/ContextFactory.html
ただし、信頼できないユーザーの場合は、これが十分ではないことに注意してください。これは、ユーザーが(偶然または故意に)大量のメモリを割り当て、JVMがOutOfMemoryErrorをスローする可能性があるためです。この最後の点に対する安全な解決策をまだ見つけていません。
非常に簡単に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上)。楽しい!
私の知る限り、Nashornをサンドボックス化することはできません。信頼できないユーザーは、次の「追加のNashorn組み込み関数」を実行できます。
https://docs.Oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/Shell.html
「quit()」を含みます。私はそれをテストしました。 JVMを完全に終了します。
(余談ですが、私のセットアップでは、グローバルオブジェクト$ ENV、$ ARGは機能しませんでした。これは良いことです。)
私がこれについて間違っている場合は、誰かがコメントを残してください。
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);
}
}
関数は現在、非推奨のスレッドstop()を使用しています。スレッドではなく、別のプロセスでJSを実行することで改善できます。
PS:ここでNashornはリフレクションを介してロードされますが、同等のJavaコードもコメントに含まれています
提供されたクラスのクラスローダーをオーバーライドすることは、クラスへのアクセスを制御する最も簡単な方法だと思います。
(免責事項:私は新しいJavaにあまり慣れていないので、この答えは古風な/時代遅れかもしれません)
Security Managerを使用しないと、NashornでJavaScriptを安全に実行できません。
Nashornを含むOracle Hotspotのすべてのリリースで、このJVMでJava/JavaScriptコードを実行するJavaScriptを記述できます。 2019年1月の時点で、Oracle Security TeamはSecurity Managerの使用が必須であると主張しています。
問題の1つはすでに https://github.com/javadelight/delight-nashorn-sandbox/issues/7 で説明されています
独自のClassLoaderとSecurityManagerを実装したくない場合は、外部サンドボックスライブラリを使用できます(これが現在のところサンドボックス化の唯一の方法です)。
「Javaサンドボックス」( http://blog.datenwerke.net/p/the-Java-sandbox.html )を試しましたが、少しですエッジの周りが荒いですが、動作します。