JavaScript沙箱的構想

來源:互聯網
上載者:User
文章目錄
  • 問題
  • 限制
  • 方案
問題

我的目標,非常簡單,就是希望能夠在我自己的系統中使用別人寫的代碼,但是這些代碼可能會汙染全域變數,甚至可能是惡意的,破壞性的。我要保證這些代碼被正確執行,並且其影響範圍完全受到控制,這就是我想要的沙箱。

根據我自己的思考以及和一些朋友的討論,我認為我主要需要解決四點:

1.變數訪問問題:第三方可以使用變數名訪問到全域變數。

2.this問題:函數執行時的預設this值就是全域變數。

3.eval和Function問題:eval可以動態地產生代碼,這些代碼只有到運行時才能確定。

4.literal以及自動裝箱問題:[] {}以及function可以構造出一些內建類的執行個體,這樣通過constructor和__proto__等能訪問到原生的全域對象。

 

限制

在這個問題中,我不希望引入過於重型的解決方案,比如,使用Narcissus之類的js引擎去執行整個代碼是可行的,但是其效能極大地限制了代碼的能力。還有,因為一些庫和架構(如wind.js)依賴某些動態特性,將eval和Function禁止也是無法接受的,甚至直接eval必須能夠訪問到其調用的上下文,這樣的特性也必須被保留。

方案變數訪問問題的解決

一些輕量級的工具(如我的JSinJS和Esprima,UglifyJS等)可以解析AST(Abstract Syntax Tree 抽象文法樹),根據抽象文法樹,可以找出所有未聲明但是已經被賦值使用的變數。

例如,以下代碼:

var a;
function my() {
    var i = j;
    j = 2;
}

 

通過AST,可以找到j 和a是被引用的全域變數。

這個問題唯一的例外是with,with中的某些變數可能並非全域:
with({s:1}) {
    s = 2;
}

因為with中的內容在運行時才能確定,所以無法預判,這裡只能按最糟糕的情況處理,認為使用了全域s。

找到了所有被引用的全域變數之後,只要用一個IFFE(Immediately Invoked Function Expression立即執行的函數運算式)把代碼套起來,並且聲明那些沒有聲明的變數,就可以把全域變數變成局部變數了:

void function(){
    var j,k; //generated from AST
    var a;
    function my() {
        var i = k;
        j = 2;
    }
}()

我們還需要暴露一些全域的方法給第三方代碼使用,在IFFE外面加一個with
with(safe_global)
void function(){ //……

safe_global的實現就可以自由定義了,暴露一些想要暴露的東西。

this問題的解決

this問題比較麻煩,在不修改代碼的情況下已知是沒有解決辦法的。this的值在運行時決定,在AST中沒有辦法知道哪些是安全的。於是我的想法是,對於所有this加一個check:例如
function f(){
    return this;
}

將會被變成

function f() {
    return _$wrap(this);
}

_$wrap函數將會檢查this是不是全域對象,必要時將其替換成 safe_window。

因為_$wrap函數同樣在運行時做檢查,所以可以有效解決this問題。

eval和Function問題的解決

eval分為直接eval和間接eval,ES規範要求直接eval必須能保留調用時的上下文,因此實現safe_eval的方式肯定是不行了(參看《無法封裝的函數:eval》)。所幸直接eval可以從AST中直接找出來,產生的程式碼必須仍然使用eval,我的方案是:

eval(……);

變成

eval(_$check(……));

_$check函數將會在運行時遞迴地做全文中所述的AST檢查,並把結果返回,這樣直接eval的問題就得以解決了。

間接eval和Function的問題類似,其代碼都是在全域執行的,問題在於我們無法從AST中直接識別出來,所以還是需要運行時處理。我的方案是把safe_global中的eval變成safe_eval。

safe_global.eval  = function safe_eval(){
    return global.eval(_$check(……));
};

Function的情況跟間接eval差不多,不多說了。

這裡還存在一個致命的問題,就是safe_global中的eval會阻止直接eval找到真正的eval函數。根據eval函數行為的定義:

一個 eval 函數的直接調用是表示為符合以下兩個條件的 CallExpression:

解釋執行 CallExpression 中的 MemberExpression 的結果是個 引用 ,這個引用擁有一個 環境記錄項 作為其基值,並且這個引用的名稱是 "eval"。

以這個 引用 作為參數調用 GetValue 抽象操作的結果是 15.1.2.1 定義的標準內建函數。

我們可以將eval(xxx)變成一個IFFE。

eval(……);

變成

(function() { var eval = _$unsafe_eval; return eval(_$check(……)); }());

這樣就儲存了上下文,這個IFFE也能像eval一樣用在運算式中。

literal以及自動裝箱問題的解決

這些同樣發生在運行時,所以無法通過AST分析來解決,因為也不可能,於是我的解決方案是在一個iframe中執行這些代碼。

唯一值得注意的是需要修改Function.prototype.constructor到safe_Function,以避免不安全的Function調用。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.