2009/12/21

Rhinoのevalを使用禁止に

サーバ側のJSで eval() を使われちゃうとセキュリティ的にどうしようも無い。
口頭で禁止しても eval() の魔力に勝てず使ってしまう輩は必ずいる。
なので物理的に禁止する事にした。

ググって見たがそれっぽい記事は出て来ない。
しかたが無いので感でグローバル変数の eval を置き換えてみたらあっさり成功。
オリジナルの eval() を保存して置けばユーザ関数の実行中のみ禁止にもできる。


ユーザ関数の起動スクリプト:

function include(pageName) {
__ENV__.include(pageName, this);
}
function XMLHttpRequest() {
return new Packages.kotemaru.wsjs.ssjs.XMLHttpRequest(__ENV__);
}

(function(req, res) {
var evalBackup = eval;
eval = function() {
throw new Packages.kotemaru.wsjs.ErrorPageException(500,"evalDisabled");
}
if (req.method == "GET") {
doGet(req, res);
} else if (req.method == "POST") {
doPost(req, res);
} else {
throw "Not imple method "+req.method
}
eval = evalBackup;
})


ユーザ関数:

function doGet(req, res) {
eval("({a:'A'})");
}



実行結果:

2009/12/21 10:22:02.249 [ERROR ] ErrorPage:500 evalDisabled[] (ssjs-boot.js#16)
org.mozilla.javascript.JavaScriptException: ErrorPage:500 evalDisabled[] (ssjs-boot.js#16)
at org.mozilla.javascript.gen.c2._c4(ssjs-boot.js:16)
at org.mozilla.javascript.gen.c2.call(ssjs-boot.js)
at org.mozilla.javascript.ScriptRuntime.callSpecial(ScriptRuntime.java:2350)
at org.mozilla.javascript.optimizer.OptRuntime.callSpecial(OptRuntime.java:165)
at org.mozilla.javascript.gen.c10._c1(/home/kotemaru.openid.ne.jp/test3.ssjs:2)
at org.mozilla.javascript.gen.c10.call(/home/kotemaru.openid.ne.jp/test3.ssjs)
at org.mozilla.javascript.optimizer.OptRuntime.callName(OptRuntime.java:97)
at org.mozilla.javascript.gen.c2._c3(ssjs-boot.js:19)
at org.mozilla.javascript.gen.c2.call(ssjs-boot.js)


簡単すぎてなんか不安だけどちゃんと動いてるみたいなのでとりあえずこれでいっちゃおーっと。



2009/12/14

RhinoでJavaオブジェクトをfor-inする

RhinoからJavaオブジェクトを操っているとJavaオブジェクトのプロパティ一覧を for-in で取りたいことが良く有る。

で、自動のラッパーでなく ScriptableObject.defineClass() を使ってちゃんと定義すればできるんだろうと思ってやってみたらできねー。

ScriptableObjectの実装クラス。

package kotemaru.wsjs;

import org.mozilla.javascript.*;

public class UserConfig extends ScriptableObject {
private int timeout = 10;
private boolean server_log = false;

public String getClassName() { return "UserConfig"; }
public int jsGet_timeout() {
return timeout;
}
public void jsSet_timeout(int t) {
timeout = t;
}

public boolean jsGet_server_log() {
return server_log;
}
public void jsSet_server_log(boolean b) {
server_log = b;
}


public int getTimeout() {
return timeout;
}
public boolean isServerLog() {
return server_log;
}
}


実行スクリプト。

(function(dest, json) {
var src = eval("("+json+")");
dest = new UserConfig();

java.lang.System.out.println("-------->for-in UserConfig ");
for (var key in dest) {
java.lang.System.out.println("-------->"+key+":"+dest[key]+":"+src[key]);
dest[key] = src[key];
}
java.lang.System.out.println("-------->end");
})


結果:空っぽ。

-------->for-in UserConfig
-------->end



しかたが無いのでRhinoのソースを見て力技で実装したのがこのコード。


package kotemaru.wsjs;

import org.mozilla.javascript.*;

public class UserConfig /*extends ScriptableObject*/ {
private int timeout = 10;
private boolean server_log = false;
public int getTimeout() {
return timeout;
}
public boolean isServerLog() {
return server_log;
}
public void setTimeout(int t) {
timeout = t;
}
public void setServer_log(boolean b) {
server_log = b;
}


private static final String[] NAMES = {
"timeout",
"server_log",
};
public java.util.Iterator __iterator__(boolean b){
return new MyIterator(NAMES);
}
public JavaScriptException __getStopIteratin() {
Context cx = Context.getCurrentContext();
Scriptable scope = cx.initStandardObjects();
return new JavaScriptException(
NativeIterator.getStopIterationObject(scope), null, 0);
}
private class MyIterator implements java.util.Iterator {
private Object[] array;
private int idx = 0;
public MyIterator(Object[] ary) {array = ary;}
public boolean hasNext(){return idx public Object next(){
if (idx>=array.length) throw __getStopIteratin();
return array[idx++];
}
public void remove(){throw new Error("Unsupport");}
}

}



実行結果:fon-inできてます。

-------->for-in UserConfig
-------->timeout:10:30
-------->server_log:undefined:true
-------->end



御覧の通りの力技なので私の試した rhino1_7R2 以外のバージョンで動くかは怪しいです。

正しいやり方をご存知の方はコメントお願いいたします。



2009/12/12

RhinoのスレッドをTimeoutさせる。

WSJSのアプリを書いていて気が付いた。
スクリプトが無限ループすると止められない。
Thread.stop()は使用禁止のお達しが出ているしどうしたものかのうとググってみたら1件だけありました。

http://hydrocul.seesaa.net/article/118727709.html

日本語の情報はここだけみたい。
オリジナルの情報は Rhino の apidoc らしい。

http://www.mozilla.org/rhino/apidocs/org/mozilla/javascript/ContextFactory.html

おおざっぱに解説すると
- Context と ContextFactory を自前で拡張実装する。
- インターバルを設定すると ContextFactory のコールバッグが呼ばれる。
- Context にタイムスタンプを持たしてコールバック内で適当に例外を上げる。
と言うことになる。

タイムアウトなんて普通に必要になりそうなんだから標準実装でもよさそうな気がするけどなぁ。
ま、それはそれとしてWSJSに組み込み。


メインクラスにこれが必要。

static {
ContextFactory.initGlobal(new SsjsContextFactory());
}


SsjsContextFactory.java:

package kotemaru.wsjs.ssjs;

import org.mozilla.javascript.*;

public class SsjsContextFactory extends ContextFactory {
private static final int LIMIT_TIME = 30*1000; //30sec

protected Context makeContext(){
SsjsContext cx = new SsjsContext();
cx.setInstructionObserverThreshold(LIMIT_TIME/10);
return cx;
}

protected Object doTopCall(Callable callable, Context cx,
Scriptable scope, Scriptable thisObj, Object[] args){
long curTime = System.currentTimeMillis();
((SsjsContext)cx).setTimeout(curTime + LIMIT_TIME);
return super.doTopCall(callable, cx, scope, thisObj, args);
}

protected void observeInstructionCount(Context cx, int instructionCount){
((SsjsContext)cx).checkTimeout();
}

}



SsjsContext.java:

package kotemaru.wsjs.ssjs;

import org.mozilla.javascript.*;
import kotemaru.wsjs.*;

public class SsjsContext extends Context {
private long timeout;

public SsjsContext() {
}
public void setTimeout(long t) {
timeout = t;
}
public void checkTimeout() {
if(timeout < System.currentTimeMillis()) {
throw new ErrorPageException(503,"JavaScriptTimeout");
}
}
}



テストコード、いつもより多く回っております。

function doGet(req, res) {
var timeout = __ENV__.currentTimeMillis() + 60*1000;
while (timeout > __ENV__.currentTimeMillis()) {
"aaa"+"bbbb";
}
}



30秒後にめでたく例外発生。

ErrorPage:503 JavaScriptTimeout[]
at kotemaru.wsjs.ssjs.SsjsContext.checkTimeout(SsjsContext.java:16)
at kotemaru.wsjs.ssjs.SsjsContextFactory.observeInstructionCount(SsjsContextFactory.java:22)
at org.mozilla.javascript.Context.observeInstructionCount(Context.java:2251)
at org.mozilla.javascript.ScriptRuntime.addInstructionCount(ScriptRuntime.java:3110)


サンプルと違いタイムアウトのチェックを Context に持って来ているのはアプリからタイムアウトを制御できるようにするため。

これができないとリモートからスクリプト記述する場合にかなり問題になるので助かった。




プロフィール
20年勤めた会社がリーマンショックで消滅、紆余曲折を経て現在はフリーランスのSE。 失業をきっかけにこのブログを始める。

サイト内検索

登録
RSS/2.0

カテゴリ

最近の投稿【Rhino】

リンク

アーカイブ