Java書けるんなら当然JavaScript呼び出せるよね?
上司「Java書けるんなら当然JavaScript呼び出せるよね?さっさとやっておいて」
JavaからJavaScriptを呼び出す
Java 6 からスクリプトAPIを通じてスクリプト言語を呼び出すことができる。Java標準でJavaScriptのエンジン Rhinoが組み込まれているので特にインストール作業やクラスパスを通すような作業なしにJavaScriptの呼び出しをすることができる。
import javax.script.*; public class Sample { public static void main(String[] args) throws ScriptException { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("js"); engine.eval("println('Hello Script API');"); } }
セキュリティ上の問題
文字列でJavaScriptのコードを渡せば呼び出すことが簡単に出来るわけだが、RhinoからはJavaのAPIも簡単に呼び出すことが出来る。
import javax.script.*; public class Sample { public static void main(String[] args) throws ScriptException { ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("js"); engine.eval("println('Hello Script API');" + "java.lang.System.exit(0)"); System.out.println("end"); } }
そして、JavaのAPIのうち、セキュリティ的に凶悪なSystem.exit()とかが普通に呼べてしまう。これはJVMを停止する命令で、外からスクリプト文字列を受け取ってRhinoで呼び出すといった使い方をする場合、重大なセキュリティ懸念となる。そうじゃなくてもやりたい放題ってのは困るんだけど。
セキュリティマネージャ
そこでセキュリティマネージャで権限をコントロールする。
AccessController.doPrivileged()を使うことで、指定権限で動作させることができる。
import java.security.*; public class Sample2 { public static void main(String[] args) { System.out.println("start"); // 外側のコードのセキュリティポリシー // このサンプルではデフォルト設定を使う Policy policy = new Policy() {}; Policy.setPolicy(policy); // setSecurityManagerをするとセキュリティマネージャが有効になる System.setSecurityManager(new SecurityManager()); // 権限を確認したい場合はコメントを外す // ProtectionDomain pd = Sample2.class.getProtectionDomain(); // System.out.println(pd); PermissionCollection permissions = new Permissions(); // ここで必要なパーミッションがあれば追加する // permissions.add(new RuntimePermission("exitVM")); // AccessControlContextを作る CodeSource codeSource = Sample2.class.getProtectionDomain().getCodeSource(); ProtectionDomain domain = new ProtectionDomain(codeSource, permissions); ProtectionDomain[] domains = new ProtectionDomain[]{domain}; AccessControlContext context = new AccessControlContext(domains); // 権限を絞って動作させる処理のラッパー PrivilegedExceptionAction<Void> action = new MyPrivilegedExceptionAction(); // 引数で渡したAccessControlContextがもつパーミッションと、 // 現在のパーミッションとの共通部分のパーミッションで実行される try { AccessController.doPrivileged(action, context); } catch (PrivilegedActionException e) { // 例外が発生した場合 e.printStackTrace(); } } /** 権限を絞って動作させる処理のラッパー */ static class MyPrivilegedExceptionAction implements PrivilegedExceptionAction<Void> { /** このメソッドが指定の権限で動作する */ @Override public Void run() throws Exception { System.out.println("in MyPrivilegedAction"); ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName("js"); // スクリプト内でSystem.exit()を呼ぶ engine.eval("println('Hello Script API');" + "java.lang.System.exit(0)"); // exit()されるのでここは実行されない System.out.println("end"); return null; } } }
MyPrivilegedExceptionAction に記載されたスクリプト呼び出し部分はSystem.exitする権限を持たない。そのため、実行すると例外が発生し、PrivilegedActionExceptionのcatchに進むだろう。
コメントを外して"exitVM"権限を与えて実行すると、スクリプト内のSystem.exit()が成功し、そこで処理が終了することが分かるだろう。