ブラウザで実行中のJavaScriptをサンドボックス化して、HTMLページで実行中のJavaScriptコードで通常使用可能な機能へのアクセスを防ぐことができるかどうか疑問に思っています。
たとえば、「興味深いイベント」が発生したときに実行されるイベントハンドラーを定義できるようにエンドユーザーにJavaScript APIを提供したいが、それらのユーザーがwindow
オブジェクト。これはできますか?
最も単純なケースでは、ユーザーがalert
を呼び出すのを防ぎたいとしましょう。私が考えることができるいくつかのアプローチは次のとおりです。
window.alert
グローバル。ページで実行されている他のコード(つまり、イベントハンドラーでユーザーが作成していないもの)がalert
を使用する可能性があるため、これは有効なアプローチではないと思います。おそらく、サーバーがユーザー定義関数を処理してから、クライアント上で実行されるコールバックを生成するソリューションは機能しますか?そのアプローチが機能していても、この問題を解決するより良い方法はありますか?
Google Caja は、「信頼されていないサードパーティのHTMLおよびJavaScriptをページにインラインで配置し、安全に保つことができる」ソースからソースへの翻訳者です。
ダグラスクロックフォードのADsafe をご覧ください。
ADsafeを使用すると、任意のWebページにゲストコード(サードパーティのスクリプト広告やウィジェットなど)を安全に配置できます。 ADsafeは、ゲストコードが価値ある相互作用を実行できると同時に、悪意のあるまたは偶発的な損傷または侵入を防ぐことができるほど強力なJavaScriptのサブセットを定義します。 ADsafeサブセットはJSLintなどのツールによって機械的に検証できるため、ゲストコードの安全性を確認するために人間による検査は必要ありません。また、ADsafeサブセットは適切なコーディング慣行を実施し、ゲストコードが正しく実行される可能性を高めます。
プロジェクトのGitHubリポジトリ のtemplate.html
およびtemplate.js
ファイルを見ると、ADsafeの使用方法の例を見ることができます。
jsandbox という名前のサンドボックスライブラリを作成しました。これは、評価されたコードをサンドボックスするためにWebワーカーを使用します。また、他の方法では取得できないサンドボックスコードデータを明示的に提供するための入力メソッドもあります。
以下はAPIの例です。
jsandbox
.eval({
code : "x=1;Math.round(Math.pow(input, ++x))",
input : 36.565010597564445,
callback: function(n) {
console.log("number: ", n); // number: 1337
}
}).eval({
code : "][];.]\\ (*# ($(! ~",
onerror: function(ex) {
console.log("syntax error: ", ex); // syntax error: [error object]
}
}).eval({
code : '"foo"+input',
input : "bar",
callback: function(str) {
console.log("string: ", str); // string: foobar
}
}).eval({
code : "({q:1, w:2})",
callback: function(obj) {
console.log("object: ", obj); // object: object q=1 w=2
}
}).eval({
code : "[1, 2, 3].concat(input)",
input : [4, 5, 6],
callback: function(arr) {
console.log("array: ", arr); // array: [1, 2, 3, 4, 5, 6]
}
}).eval({
code : "function x(z){this.y=z;};new x(input)",
input : 4,
callback: function(x) {
console.log("new x: ", x); // new x: object y=4
}
});
[〜#〜] edit [〜#〜]:以下のホワイトリストをエスケープする方法はわかりませんが、ワーカーを実行しますサンドボックスから<iframe>
も念のため、メモリの枯渇が懸念される場合は、@ gronostajのjs.jsの推奨を2番目に推奨します。
Web workers 別のスクリプトコンテキストを作成する便利な方法を提供します。これにより、親に影響を与えることなく積極的にサンドボックス化できます。
Promiseを使用した実装:
function safeEval(untrustedCode) {
return new Promise(function (resolve, reject) {
var worker = new Worker('eval.js');
worker.onmessage = function (e) {
worker.terminate();
resolve(e.data);
};
worker.onerror = function (e) {
reject(new Error(e.message));
};
worker.postMessage(untrustedCode);
setTimeout(function () {
worker.terminate();
reject(new Error('The worker timed out.'));
}, 1000);
});
}
eval.js
(おそらくホワイトリストを拡張したいでしょう):
(function (global) {
'use strict';
var _postMessage = postMessage;
var _addEventListener = addEventListener;
(function () {
var current = global;
var keepProperties = [
// required
'Object', 'Function', 'Infinity', 'NaN', 'undefined',
// optional, but trivial to get back
'Array', 'Boolean', 'Number', 'String', 'Symbol',
// optional
'Map', 'Math', 'Set',
];
do {
Object.getOwnPropertyNames(current).forEach(function (name) {
if (keepProperties.indexOf(name) === -1) {
delete current[name];
}
});
current = Object.getPrototypeOf(current);
} while (current !== Object.prototype);
})();
_addEventListener('message', function (e) {
var f = new Function('', 'return (' + e.data + '\n);');
_postMessage(f());
});
})(this);
ワーカーからのUIアクセスはなく、個別に終了でき、UIまたは実行中のスクリプトを拘束できず、標準です。
js.js はここで言及する価値があると思います。これは、JavaScriptで記述されたJavaScriptインタープリターです。
ネイティブJSの約200倍遅いですが、その性質により、完璧なサンドボックス環境になります。もう1つの欠点はそのサイズです。ほぼ600 kbであり、モバイルデバイスではなく、デスクトップでは許容される場合があります。
他の回答で述べたように、サンドボックス化されたiframe内のコードを(サーバー側に送信せずに)投獄し、メッセージと通信するだけで十分です。 小さなライブラリ を参照することをお勧めします。質問で説明したように、信頼できないコードにAPIを提供する必要があるため、主に作成しました。特定のセットをエクスポートする機会があります信頼できないコードが実行されるサンドボックスへの関数の直接。また、サンドボックスでユーザーが送信したコードを実行するデモもあります。
すべてのブラウザベンダーとHTML5仕様は、サンドボックス化されたiframeを許可する実際のサンドボックスプロパティに取り組んでいますが、それでもiframeの粒度に制限されています。
一般に、JavaScriptが提供する任意のユーザーが停止問題を悪化させるので、正規表現などの程度は安全にサニタイズできません:-/
@RyanOHaraのWebワーカーサンドボックスコードの改良バージョン(単一ファイル(余分なeval.js
ファイルは必要ありません)。
function safeEval(untrustedCode)
{
return new Promise(function (resolve, reject)
{
var blobURL = URL.createObjectURL(new Blob([
"(",
function ()
{
var _postMessage = postMessage;
var _addEventListener = addEventListener;
(function (obj)
{
"use strict";
var current = obj;
var keepProperties = [
// required
'Object', 'Function', 'Infinity', 'NaN', 'undefined', 'caches', 'TEMPORARY', 'PERSISTENT',
// optional, but trivial to get back
'Array', 'Boolean', 'Number', 'String', 'Symbol',
// optional
'Map', 'Math', 'Set',
];
do {
Object.getOwnPropertyNames(current).forEach(function (name) {
if (keepProperties.indexOf(name) === -1) {
delete current[name];
}
});
current = Object.getPrototypeOf(current);
}
while (current !== Object.prototype);
})(this);
_addEventListener("message", function (e)
{
var f = new Function("", "return (" + e.data + "\n);");
_postMessage(f());
});
}.toString(),
")()"], {type: "application/javascript"}));
var worker = new Worker(blobURL);
URL.revokeObjectURL(blobURL);
worker.onmessage = function (evt)
{
worker.terminate();
resolve(evt.data);
};
worker.onerror = function (evt)
{
reject(new Error(evt.message));
};
worker.postMessage(untrustedCode);
setTimeout(function () {
worker.terminate();
reject(new Error('The worker timed out.'));
}, 1000);
});
}
試して:
https://jsfiddle.net/kp0cq6yw/
var promise = safeEval("1+2+3");
promise.then(function (result) {
alert(result);
});
6
を出力するはずです(ChromeおよびFirefoxでテスト済み)。
い方法ですが、多分これはあなたのために働くでしょう、私はすべてのグローバルを取得してサンドボックススコープで再定義しました。また、匿名関数を使用してグローバルオブジェクトを取得できないように厳格モードを追加しました。
function construct(constructor, args) {
function F() {
return constructor.apply(this, args);
}
F.prototype = constructor.prototype;
return new F();
}
// Sanboxer
function sandboxcode(string, inject) {
"use strict";
var globals = [];
for (var i in window) {
// <--REMOVE THIS CONDITION
if (i != "console")
// REMOVE THIS CONDITION -->
globals.Push(i);
}
globals.Push('"use strict";\n'+string);
return construct(Function, globals).apply(inject ? inject : {});
}
sandboxcode('console.log( this, window, top , self, parent, this["jQuery"], (function(){return this;}()));');
// => Object {} undefined undefined undefined undefined undefined undefined
console.log("return of this", sandboxcode('return this;', {window:"sanboxed code"}));
// => Object {window: "sanboxed code"}
独立したJavascriptインタープリターは、組み込みブラウザー実装のケージバージョンよりも堅牢なサンドボックスを生成する可能性が高くなります。 Ryanには 既に言及js.js がありますが、より最新のプロジェクトは JS-Interpreter です。 docs は、さまざまな関数をインタープリターに公開する方法をカバーしていますが、それ以外の場合、そのスコープは非常に制限されています。
2019年現在、 vm2 は、この問題に対する最も人気があり、最も定期的に更新されるソリューションのようです。