ブラウザでJavaScriptをサンドボックスで実行することは可能ですか?


142

ブラウザで実行されているJavaScriptをサンドボックス化して、HTMLページで実行されているJavaScriptコードで通常使用できる機能へのアクセスを防ぐことができるかどうか疑問に思っています。

たとえば、「興味深いイベント」が発生したときに実行されるイベントハンドラーをエンドユーザーが定義できるようにJavaScript APIを提供したいが、それらのユーザーにwindowオブジェクトのプロパティと関数にアクセスさせたくないとします。これはできますか?

最も単純なケースでは、ユーザーがを呼び出せないようにしたいとしますalert。私が考えることができるいくつかのアプローチは:

  • window.alertグローバルに再定義します。ページで実行されている他のコード(つまり、ユーザーがイベントハンドラーで作成していないもの)がを使用する可能性があるため、これは有効なアプローチではないと思いますalert
  • イベントハンドラーコードをサーバーに送信して処理します。イベントハンドラーはページのコンテキストで実行する必要があるため、サーバーにコードを送信して処理するのが適切な方法であるかどうかはわかりません。

おそらく、サーバーがユーザー定義関数を処理し、クライアントで実行されるコールバックを生成するソリューションは機能するでしょうか?そのアプローチが機能しても、この問題を解決するより良い方法はありますか?

回答:


54

Google Cajaはソースからソースへのトランスレータであり、「信頼できないサードパーティのHTMLとJavaScriptをページにインラインで配置して、引き続き安全にすることができます。」


5
簡単なテストは、CajaがブラウザをCPU攻撃から保護できないことを示していwhile (1) {}ます---ハングするだけです。同様にa=[]; while (1) { a=[a,a]; }
Davidが

5
はい、サービス拒否は範囲外です:code.google.com/p/google-caja/issues/detail?id
Darius Bacon

32

見ていダグラス・クロックフォードさんのAdSafeを

ADsafeを使用すると、ゲストコード(サードパーティのスクリプト広告やウィジェットなど)を任意のWebページに安全に配置できます。ADsafeは、ゲストコードが貴重な対話を実行できると同時に、悪意のある、または偶発的な損傷や侵入を防止できるほど強力なJavaScriptのサブセットを定義します。ADsafeサブセットはJSLintなどのツールで機械的に検証できるため、ゲストコードを確認して安全を確認するために人間による検査は必要ありません。ADsafeサブセットは適切なコーディングプラクティスを実施するため、ゲストコードが正しく実行される可能性が高くなります。

プロジェクトのGitHubリポジトリにあるtemplate.htmlおよびtemplate.jsファイルを確認することで、ADsafeの使用例を確認できます


彼らのサイトでは、ADsafeの使い方はわかりません。それをダウンロードする方法、コードへのリンク、何もありません。どのようにしてADsafeを試すことができますか?
BT

2
また、それは防ぎいかなるへのアクセスをthis完全に受け入れられないです。を使用せずに優れたJavaScriptを作成することはできませんthis
BT

4
@BT私はを使用せずにプロジェクト全体を書きましたthis。名前の正しくないパラメーターを回避することは難しくありません。
soundly_typed 2017年

2
@BT実世界のプロジェクトを完了することは受け入れられないと言うのはばかげたことでしょう。しかし、私はこの議論を始めたことを後悔し、撤回しなければなりません。ここはそのようなことを議論する場所ではありません(申し訳ありません)。詳細については、ツイッターを利用しています。
soundly_typed 2017年

1
@BT(質問に関連するので続けます)他の誰かの環境でコードを実行するときはいつでも、ルールと制限に遭遇します。私はそれを容認できないとは言いません。「お尻の痛み」かもしれません。しかし、受け入れられません。結局のところ、を使用するたびにthis、同等の、同等のthis方法でそれを行うことができます(結局のところ、これは単なるパラメーターです)。
soundly_typed 2017年

24

評価されたコードをサンドボックス化するためにWebワーカーを使用するjsandboxと呼ばれるサンドボックス化ライブラリを作成しました。また、他の方法では取得できないサンドボックス化されたコードデータを明示的に指定するための入力メソッドもあります。

以下は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
      }
  });

+1:これは本当にクールに見えます。この方法でユーザーのコードを実行することは安全ですか?
Konstantin

1
非常に安全。githubで更新されたライブラリ確認してください。
Eli Grey

1
このプロジェクトはまだ維持されていますか?2年以上更新されていないようです...
Yanick Rochon、2012年

私はこれが好きですが、サンドボックス化したいがコードアクセスにjQueryを許可する場合は、WebワーカーがDOM操作を許可しないため、これは失敗します。
Rahly

こんにちはエリ-素晴らしいlibをありがとう、あなたはそれを維持するつもりですか?私はデバッグ機能を追加するための変更要求を持っています-これはコードをすばやく見ることで可能になるはずです。ご意見をお聞かせください。
user1514042

8

js.jsはここで言及する価値があると思います。JavaScriptで書かれたJavaScriptインタプリタです。

ネイティブJSの約200倍の速度ですが、その性質上、完璧なサンドボックス環境です。もう1つの欠点はそのサイズです。約600 kbで、デスクトップでは許容される場合がありますが、モバイルデバイスでは許容されません。


7

他の応答で述べたように、サンドボックス化されたiframeにコードを(サーバー側に送信せずに)投獄し、メッセージと通信するだけで十分です。質問で説明されているように、信頼できないコードにいくつかのAPIを提供する必要があるため、私が作成した小さなライブラリを見てみることをお勧めします。特定の関数セットをサンドボックスに直接エクスポートする機会があります。信頼できないコードが実行されます。また、サンドボックスでユーザーが送信したコードを実行するデモもあります。

http://asvd.github.io/jailed/demos/web/console/


4

すべてのブラウザーベンダーとHTML5仕様は、サンドボックス化されたiframeを許可する実際のサンドボックスプロパティに向けて取り組んでいますが、それでもiframeの粒度に制限されています。

一般に、停止問題に退化するため、正規表現などでユーザーが指定した任意のJavaScriptを安全に無害化することはできません。


2
それが停止問題にどのように悪化するか説明できますか?
hdgarrood 2013

2
停止問題を解決することの理論的不可能性は、静的コード分析にのみ当てはまります。サンドボックスは、停止の問題に対処するために時間制限を強制するようなことを行うことができます。
Aviendha 2015年

4

@RyanOHaraのWebワーカーサンドボックスコードの改良版が1つのファイルに含まれています(追加の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でテスト済み)。


2

醜い方法ですが、これでうまくいくかもしれません。すべてのグローバルを取得し、サンドボックススコープで再定義しました。また、匿名関数を使用してグローバルオブジェクトを取得できないように厳格モードを追加しました。

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"}

https://gist.github.com/alejandrolechuga/9381781


3
windowそれから戻るのは簡単です。sandboxcode('console.log((0,eval)("this"))')
Ry-

私はそれを防ぐ方法を理解する必要があります
アレハンドロ

@alejandroそれを防ぐ方法を見つけましたか?
Wilt

1
私の実装は単に追加します:function sbx(s,p) {e = eval; eval = function(t){console.log("GOT GOOD")}; sandboxcode(s,p); eval =e}
YoniXw 2018

2
@YoniXw:何かのためにそれを使用していないことを願っています。このようなアプローチは機能しません。(_=>_).constructor('return this')()
Ry-

1

独立したJavaScriptインタープリターは、組み込みブラウザー実装のケージバージョンよりも堅牢なサンドボックスを生成する可能性が高くなります。Ryanはすでに js.js について言及しいますが、より最新のプロジェクトはJS-Interpreterです。ドキュメントはインタプリタにさまざまな機能を公開する方法をカバーしますが、その範囲は、それ以外の場合は非常に限られています。


1

2019年の時点で、vm2はこの問題に対する最も一般的で最も定期的に更新されるソリューションのようです。


vm2はブラウザーのランタイムをサポートしていません。ただし、nodejsアプリでサンドボックスコードを探している場合は機能します。
kevin.groat

0

ではNISPあなたはサンドボックスの評価を行うことができるでしょう。記述する式は厳密にはJSではありませんが、代わりにs式を記述します。大規模なプログラミングを必要としないシンプルなDSLに最適です。


-3

1)実行するコードがあるとします。

var sCode = "alert(document)";

ここで、それをサンドボックスで実行するとします。

new Function("window", "with(window){" + sCode + "}")({});

「サンドボックス」から「アラート」機能を利用できないため、これらの2行は実行時に失敗します。

2)そして、あなたはあなたの機能でウィンドウオブジェクトのメンバーを公開したいと思います:

new Function("window", "with(window){" + sCode + "}")({
    'alert':function(sString){document.title = sString}
});

確かにあなたはエスケープを引用し、他の磨きをかけることができますが、私はその考えは明らかだと思います。


7
グローバルオブジェクトに到達するための無数の他の方法はありませんか?たとえば、func.apply(null)を使用して呼び出される関数内では、「this」がウィンドウオブジェクトになります。
mbarkhau 2011

5
最初の例は失敗しません。これはサンドボックス化の非常に無効な例です。
アンディE

1
var sCode = "this.alert( 'FAIL')";
Leonard Pauli 2013

-4

このユーザーJavaScriptはどこから来たのですか?

ユーザーがページにコードを埋め込み、それをブラウザーから呼び出すことについてできることはあまりありません(Greasemonkey、http: //www.greasespot.net/を参照)。これはブラウザが行うことです。

ただし、スクリプトをデータベースに格納し、それを取得してeval()する場合は、実行する前にスクリプトをクリーンアップできます。

すべてのウィンドウを削除するコードの例。とドキュメント。参照:

 eval(
  unsafeUserScript
    .replace(/\/\/.+\n|\/\*.*\*\/, '') // Clear all comments
    .replace(/\s(window|document)\s*[\;\)\.]/, '') // removes window. or window; or window)
 )

これにより、以下が実行されないようにします(テストされていません)。

window.location = 'http://mydomain.com';
var w = window  ;

安全でないユーザースクリプトに適用する必要がある多くの制限があります。残念ながら、JavaScriptで使用できる「サンドボックスコンテナ」はありません。


2
誰かが悪意のあることを行おうとしている場合、単純な正規表現はそれを行うことができません-(function(){this ["loca" + "tion"] = " example.com ";})()ユーザーを信頼できない(任意のユーザーがコンテンツを追加できるサイトの場合など)すべてのjsをブロックする必要があります。
olliej 2008年

過去に似たようなものを使ったことがあります。完璧ではありませんが、ほとんどの方法でそこに行き着きます。
Sugendran 2008年

olliej、あなたはそのようなテクニックの限界について正しいです。<code> var window = null、document = null、this = {}; </ code>のようなグローバル変数を上書きしてみませんか?
Dimitry

Dimitry Z、これらの変数の上書きは許可されていません(一部のブラウザーでは)。また、回答のリストで私の解決策を確認してください-うまくいきます。
Sergey Ilinsky、2008年

-5

ユーザーが自分のサイトのアプレットを作成できるようにするために、私は単純なjsサンドボックスに取り組んでいます。私はまだDOMアクセスを許可することでいくつかの課題に直面していますが(parentNodeは物事を安全に保つことを許可しない= /)、私のアプローチは、ウィンドウオブジェクトをその有用/無害なメンバーで再定義し、次にユーザーをeval()することでしたこの再定義されたウィンドウをデフォルトのスコープとして使用するコード。

私の「コア」コードは次のようになります...(完全には表示していません;)

function Sandbox(parent){

    this.scope = {
        window: {
            alert: function(str){
                alert("Overriden Alert: " + str);
            },
            prompt: function(message, defaultValue){
                return prompt("Overriden Prompt:" + message, defaultValue);
            },
            document: null,
            .
            .
            .
            .
        }
    };

    this.execute = function(codestring){

        // here some code sanitizing, please

        with (this.scope) {
            with (window) {
                eval(codestring);
            }
        }
    };
}

したがって、Sandboxをインスタンス化し、そのexecute()を使用してコードを実行できます。また、evalされたコード内で宣言されたすべての新しい変数は、最終的にはexecute()スコープにバインドされるため、名前の衝突や既存のコードの混乱は発生しません。

グローバルオブジェクトは引き続きアクセス可能ですが、サンドボックス化されたコードにとって未知のままであるべきオブジェクトは、Sandbox :: scopeオブジェクトでプロキシとして定義する必要があります。

これがうまくいくことを願っています。


8
これは何もサンドボックス化しません。評価されたコードは、メンバーを削除してそのグローバルスコープに到達するか、または(function(){return this;})()を実行してグローバルスコープへの参照を取得します
Mike Samuel

-6

禁止されたオブジェクトをパラメーターとして再定義する関数でユーザーのコードをラップすることができます-これらはundefined呼び出されたときに行われます:

(function (alert) {

alert ("uh oh!"); // User code

}) ();

もちろん、巧妙な攻撃者は、JavaScript DOMを検査して、ウィンドウへの参照を含むオーバーライドされていないオブジェクトを見つけることで、これを回避できます。


別のアイデアは、jslintのようなツールを使用してユーザーのコードをスキャンすることです。事前設定された変数がない(または必要な変数のみが含まれる)ように設定されていることを確認してください。グローバルが設定またはアクセスされている場合は、ユーザーのスクリプトを使用しないでください。繰り返しになりますが、DOMのウォークに対して脆弱である可能性があります。ユーザーがリテラルを使用して構築できるオブジェクトには、サンドボックスをエスケープするためにアクセスできるウィンドウオブジェクトへの暗黙的な参照がある可能性があります。


2
ユーザーがプレーンアラートではなくwindow.alertを入力した場合、その制限をバイパスします。
クエンティン

@Dorward:はい、したがって「禁止されたオブジェクト」。wrunsbyは、ユーザーがアクセスを許可されていないオブジェクトを決定し、それらをパラメーターリストに配置する必要があります。
ジョンミリキン、

オブジェクトは1つしかありません-ウィンドウ。それへのアクセスをブロックしない場合、すべてがそこから利用できます。ブロックすると、スクリプトはそのプロパティのいずれにもアクセスできなくなります(window.alertの代わりにalertと言うと、ウィンドウを暗示するだけです)。
Quentin

@Doward:window.alertをブロックすることはできませんが、アラートは機能します。試してみてください。これは、ウィンドウがグローバルオブジェクトでもあるためです。ユーザーコードにアクセスさせたくないウィンドウとウィンドウのプロパティまたはメソッドをブロックする必要があります。
AnthonyWJones 2008年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.