簡體   English   中英

將 eval() 限制在一個狹窄的范圍內

[英]Restricting eval() to a narrow scope

我有一個 javascript 文件,它讀取另一個文件,該文件可能包含需要 eval()-ed 的 javascript 片段。 腳本片段應該符合嚴格的 javascript 子集,它限制了它們可以做什么以及它們可以更改哪些變量,但我想知道是否有某種方法可以通過阻止 eval 看到全局范圍內的變量來強制執行此操作. 類似於以下內容:

function safeEval( fragment )
{
    var localVariable = g_Variable;

    {
        // do magic scoping here so that the eval fragment can see localVariable
        // but not g_Variable or anything else outside function scope

        eval( fragment );
    }
}

實際的代碼不需要看起來像這樣——我對任何和所有奇怪的閉包技巧都持開放態度,等等。但我確實想知道這是否可能

簡短的回答:不。如果它在全球范圍內,它對任何東西都可用。

長答案:如果您正在eval()輸入真正想要讀取或弄亂您的執行環境的不受信任的代碼,那么您就完蛋了。 但是,如果您擁有並信任所有正在執行的代碼,包括被eval()編輯的代碼,您可以通過覆蓋執行上下文來偽造它:

function maskedEval(scr)
{
    // set up an object to serve as the context for the code
    // being evaluated. 
    var mask = {};
    // mask global properties 
    for (p in this)
        mask[p] = undefined;

    // execute script in private context
    (new Function( "with(this) { " + scr + "}")).call(mask);
}

再次,我必須強調:

這只會將受信任的代碼從執行它的上下文中屏蔽掉。 如果您不信任代碼,請不要eval()它(或將其傳遞給 new Function() ,或以任何其他行為類似於eval()的方式使用它)。

Shog9♦ 的回答很棒。 但是,如果您的代碼只是一個表達式,則代碼將被執行並且不會返回任何內容。 對於表達式,使用

function evalInContext(context, js) {

  return eval('with(context) { ' + js + ' }');

}

以下是如何使用它:

var obj = {key: true};

evalInContext(obj, 'key ? "YES" : "NO"');

它將返回"YES"

如果不確定要執行的代碼是表達式還是語句,可以將它們組合起來:

function evalInContext(context, js) {

  var value;

  try {
    // for expressions
    value = eval('with(context) { ' + js + ' }');
  } catch (e) {
    if (e instanceof SyntaxError) {
      try {
        // for statements
        value = (new Function('with(this) { ' + js + ' }')).call(context);
      } catch (e) {}
    }
  }

  return value;
}

與上述with塊方法中的動態函數包裝腳本類似,這允許您將偽全局變量添加到要執行的代碼中。 您可以通過將特定事物添加到上下文中來“隱藏”它們。

function evalInContext(source, context) {
    source = '(function(' + Object.keys(context).join(', ') + ') {' + source + '})';

    var compiled = eval(source);

    return compiled.apply(context, values());

    // you likely don't need this - use underscore, jQuery, etc
    function values() {
        var result = [];
        for (var property in context)
            if (context.hasOwnProperty(property))
                result.push(context[property]);
        return result;
    }
}

有關示例,請參見http://jsfiddle.net/PRh8t/ 請注意,並非所有瀏覽器都支持Object.keys

有一個名為 Google Caja 的項目。 您可以使用 Caja “沙盒”第三方 javascript。 https://developers.google.com/caja/

這是一個想法。 如果您使用靜態分析器(例如,您可以使用esprima構建的東西)來確定 eval'd 代碼使用哪些外部變量,並為它們取別名。 “外部代碼”是指 eval'd 代碼使用但未聲明的變量。 這是一個例子:

eval(safeEval(
     "var x = window.theX;"
    +"y = Math.random();"
    +"eval('window.z = 500;');"))

其中 safeEval 返回使用阻止訪問外部變量的上下文修改的 javascript 字符串:

";(function(y, Math, window) {"
  +"var x = window.theX;"
  +"y = Math.random();"
  +"eval(safeEval('window.z = 500;');"
"})();"

你現在可以用這個做幾件事:

  • 您可以確保 eval'd 代碼不能讀取外部變量的值,也不能寫入它們(通過將undefined作為函數參數傳遞,或不傳遞參數)。 或者,在不安全地訪問變量的情況下,您可以簡單地拋出異常。
  • 您還確保由 eval 創建的變量不會影響周圍的范圍
  • 您可以通過在閉包之外聲明這些變量而不是函數參數來允許 eval 在周圍范圍內創建變量
  • 您可以通過復制外部變量的值並將它們用作函數的參數來允許只讀訪問
  • 您可以通過告訴 safeEval 不對這些特定名稱起別名來允許對特定變量的讀寫訪問
  • 您可以檢測 eval修改特定變量的情況,並允許自動將其排除在別名之外(例如,在這種情況下,Math 沒有被修改)
  • 您可以通過傳入可能與周圍上下文不同的參數值來為 eval 提供運行的上下文
  • 您還可以通過從函數返回函數參數來捕獲上下文更改,以便您可以在 eval 之外檢查它們。

請注意,使用eval是一種特殊情況,因為就其性質而言,它實際上不能被包裝在另一個函數中(這就是我們必須執行eval(safeEval(...))的原因)。

當然,完成所有這些工作可能會減慢您的代碼速度,但肯定有一些地方的命中無關緊要。 希望這可以幫助某人。 如果有人創建了概念證明,我很樂意在這里看到它的鏈接; )

不要執行你不信任的代碼。 全局變量將始終可訪問。 如果您確實信任該代碼,則可以使用其范圍內的特定變量執行它,如下所示:

(new Function("a", "b", "alert(a + b);"))(1, 2);

這相當於:

(function (a, b) {
    alert(a + b);
})(1, 2);

你不能限制 eval 的范圍

順便說一句,看到這個帖子

在宏偉的計划中,可能有其他方法可以完成您想要完成的事情,但您不能以任何方式限制 eval 的范圍。 您也許可以在 javascript 中將某些變量隱藏為偽私有變量,但我認為這不是您想要的。

不要使用eval 還有一個替代方案, js.js用 JS 編寫的 JS 解釋器,這樣您就可以在您設法設置的任何環境中運行 JS 程序。 以下是項目頁面中其 API 的示例:

var jsObjs = JSJS.Init();
var rval = JSJS.EvaluateScript(jsObjs.cx, jsObjs.glob, "1 + 1");
var d = JSJS.ValueToNumber(jsObjs.cx, rval);
window.alert(d); // 2
JSJS.End(jsObjs);

如您所見,沒什么可怕的。

我偶然發現我可以使用 Proxy 來限制范圍對象,將變量屏蔽到范圍之外似乎要容易得多。 我不確定這種方法是否有缺點,但到目前為止它對我來說效果很好。

function maskedEval(src, ctx = {})
{
    ctx = new Proxy(ctx, {
        has: () => true
    })
    // execute script in private context
    let func = (new Function("with(this) { " + src + "}"));
    func.call(ctx);
}
a = 1;
maskedEval("console.log(a)", { console });
maskedEval("console.log(a)", { console, a: 22});

maskedEval("a = 1", { a: 22 })
console.log(a)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM