次のコードはタイムアウトしません。
const {VM} = require('vm2');
new VM({timeout:1}).run(`
function main(){
while(1){}
}
new Proxy({}, {
getPrototypeOf(t){
global.main();
}
})`);
これに対する良い解決策はおそらくありません。 NodeJSは単一のプロセスです。 while(1){}保護には、チェッカーがwhile(1){}によってブロックされないように、マルチプロセスが必要です。 たぶん、SpiderNodeプロジェクトを再燃させることができれば……。
これはによって引き起こされるDecontextify.value
でゲスト・オブジェクトのプロトタイプをテストinstanceOf
。 Node.js vm
モジュールは、もちろんこれの影響を受けません。 貧乏人のパッチは、サンドボックスからプロキシオブジェクトを検出できるようにProxy
をバックドアすることかもしれませんが、理想的なアプローチは、そこからのオブジェクトと関数を決して信頼せず、contextify.jsをサンドボックスに入れることです。プリミティブ値を使用して通信し、ゲストオブジェクトを参照するために処理します。
人々が見るためだけに、browserlessは子プロセスとメッセージパッシングを使用して、信頼できないコードを実行している子と通信します。 これは少しセットアップ作業ですが、一度実行すると、 while(1){}
攻撃の影響を受けなくなります。 ソースはこちらです。
このプロジェクトの要点は、まったく別のものを生み出さないことだと思われます
OSプロセス。 そうでなければ、多くの利用可能な多くのソリューションがあります
より安全に。
それ以外の場合は、はるかに安全な多くのソリューションが利用可能です。
どれ? NodeJSにVM2のより良い代替手段があるかどうかはわかりませんが、少なくとも私が見たことはありません
子プロセスで信頼できないコードのみを実行します。 特にシングルスレッドであることがわかっている場合は、メインのV8インスタンスで実行するリスクを冒すことはできません。
V8エンジンを直接呼び出す際の問題は、モジュールをインポートできないことです。 私の場合、HTTP操作を許可するためにリクエストモジュールをインポートする必要があります。 また、非同期である必要があります。
vm2 + child_processが良い組み合わせだと思います。
ねえ、
私と@harelmoは、 while(1) {}
影響を受けないvm2上にライブラリを作成しました。
tian-maが述べたものと似ていますが、子プロセスの代わりにnode.jsワーカースレッドを使用します。
https://www.npmjs.com/package/isolated-runtimeをご覧になり、ご意見をお聞かせください:)
別の回避策は、サンドボックス内でProxy
無効にすることです。
const vm = new VM({
sandbox: {
Proxy: undefined
},
timeout: 100
});
Promiseを使用すると、タイムアウトしないコードを作成することもできます。
"use strict";
const {VM} = require('vm2');
const untrusted = '(' + function(){
Promise.resolve().then(a=>{
while(1){}
});
}+')()';
try{
console.log(new VM({timeout:10}).run(untrusted));
}catch(x){
console.log(x);
}
ここでの問題は、後でマイクロタスクキューで呼び出される関数をキューに入れることです。
これを修正するには、独自のPromise.thenと関連関数を作成するか、Promiseを無効にする必要があります。
また、これは機能します:
const {VM} = require('vm2');
const script = '(' + function() {
(async function x(){
await {then: y=>y()};
while(1){}
})();
}+')()';
new VM({timeout:10}).run(script);
したがって、Promiseをオーバーライドするだけでは機能しません。
これを修正する方法がわかりません。
これは開いたままですが、潜在的なユーザーに警告するためにREADMEに説明を追加する必要があります。 この脆弱性により、サンドボックスから脱出できるため、ライブラリの目的が損なわれます。
Readmeが更新されましたhttps://github.com/patriksimek/vm2/commit/77f5265ab53b87864a312156ee62b1082787e9b0#diff-04c6e90faac2675aa89e2176d2eec7d8 。 そして、これを使用してサンドボックスをエスケープする方法がわかりません。スクリプトを実行する予定の時間枠をエスケープすることしかできません。
おそらくこれはそれ自体の問題であるはずですが、NodeVMが「タイムアウト」をサポートしないのはなぜですか?
@crowder NodeVMにはsetTimeout、setInterval、およびsetImmediateが存在するため、タイムアウトを回避するのは簡単です。 他の理由もあるかもしれませんが、私はNodeVMを使用せず、VMのみを使用しています。
@XmiliaHは、タイムアウトを実装することで、少なくとも意図しない無限ループを防止しませんか?
悪意のあるwhile(true)+ setTimeoutの組み合わせは依然として問題ですが、誤って行われた単純な意図しない無限ループは、タイムアウトをサポートすることで処理できます。
私の懸念は、NodeVMがホストモジュールである可能性のあるモジュールを要求することを許可し、それらがグローバル状態を変更する間にタイムアウトすることが悪い可能性があることです。 また、NodeVMはモジュールを返し、ユーザーはモジュールによって公開された関数を呼び出す可能性が非常に高くなりますが、タイムアウトはモジュールの読み込みにのみ適用され、関数呼び出しには適用されません。
タイムアウト付きの関数を呼び出したい場合は、次のように自分で実装できます。
function doWithTimeout(fn, timeout) {
let ctx = CACHE.timeoutContext;
let script = CACHE.timeoutScript;
if (!ctx) {
CACHE.timeoutContext = ctx = vm.createContext();
CACHE.timeoutScript = script = new vm.Script('fn()', {
filename: 'timeout_bridge.js',
displayErrors: false
});
}
ctx.fn = fn;
try {
return script.runInContext(ctx, {
displayErrors: false,
timeout
});
} finally {
ctx.fn = null;
}
}
良い点@XmiliaHと例をありがとう
あなたの例はvm2ではなくvmパッケージを使用しているようです
vm2を使用でき、それでもタイムアウトできると便利です。
@szydan私が投稿したコードは、VMをタイムアウトするためにVM2によって使用されます。 NodeVMにもタイムアウトがある場合、同じ機能を使用します。