【问题标题】:Semi-sandboxing Javascript eval半沙箱 Javascript 评估
【发布时间】:2012-09-02 07:31:42
【问题描述】:

背景:我正在开发一个框架/库,用于与greasemonkey/userscripts 协调的特定站点。这个框架/库将允许插件支持。它的工作方式是在库中注册一个插件,列出所需的页面、资源等,并且该库将等到满足所有条件后才调用插件的 load() 函数。

问题:在这个“必需的东西”列表中,我希望插件开发人员能够指定要评估为“必需资源”的 javascript(作为字符串)。例如'document.getElementById("banana")'。我想要做的是半沙箱评估“所需资源”,因此评估可以访问窗口和 DOM 对象,但不能直接更改它们。我还想让 eval 和 evalJS 无法从沙箱中访问。


示例

  • document.getElementById("banana") -> 有效
  • document.getElementById("apple).id = "orange" -> 无效
  • window.grape -> 有效
  • window.grape = 'potato' -> 无效
  • (someObj.applesCount > 0 ? 'some' : 'none') -> 有效


我目前所拥有的

function safeEval(input) {

    // Remove eval and evalJS from the window:
    var e = [window.eval, window.evalJS], a;
    window.eval = function(){};
    window.evalJS = function(){};

    try {

        /* More sanition needed before being passed to eval */

        // Eval the input, stuffed into an annonomous function
        // so the code to be evalued can not access the stored
        // eval functions:
        a = (e[0])("(function(){return "+input+"}())");
    } catch(ex){}

    // Return eval and evalJS to the window:
    window.eval = e[0];
    window.evalJS = e[1];

    // Return the eval'd result
    return a;
}


注意事项
这是一个 Greasemonkey/用户脚本。我没有直接访问权限来更改网站,或者它是 javascript。
safeEval() 的输入可以是任何有效的 javascript,可以是 DOM 查询,也可以是简单的求值,只要它不改变窗口对象或 DOM。

【问题讨论】:

  • 如果是我的网络服务器从第三方提取代码,Caja 就很好。但是greasemonkey/userscripts 是客户端的,并且在已经装满它们的站点上需要另一个库/小部件并不是我愿意接受这个问题的解决方案。 (我很抱歉,如果这突然出现,我不是这个意思)
  • 看起来 Caja 从 Greasemonkey 使用起来相当简单:一个 @require 指令和一些对其 API 的 GM_xhr 调用。不过,不确定 Caja 是否符合您的要求。
  • Chrome 和 Opera 的用户脚本支持不强制使用 @require 标签,遗憾的是
  • 关于深度克隆 windowdocument 的想法?这样,属性将是可更改的,但您可以在之后恢复原始 DOM。不过,可能会影响性能。

标签: javascript eval greasemonkey sandbox


【解决方案1】:

没有绝对的方法可以阻止最终用户或插件开发人员在 JavaScript 中执行特定代码。这就是为什么说像 JavaScript 这样的开源语言中的安全措施是万无一失的(因为它只对傻瓜有效)。

话虽如此,但让我们构建一个沙盒安全层,以防止没有经验的开发人员破坏您的网站。我个人更喜欢使用Function 构造函数而不是eval 来执行用户代码,原因如下:

  1. 代码被包装在一个匿名函数中。因此,它可以存储在一个变量中,并根据需要多次调用。
  2. 函数始终存在于全局范围内。因此它无法访问创建函数的块中的局部变量。
  3. 函数可以传递任意命名参数。因此,您可以利用此功能来传递或导入用户代码所需的模块(例如jQuery)。
  4. 最重要的是,您可以设置自定义this 指针并创建名为windowdocument 的局部变量,以防止访问全局范围和DOM。这允许您创建自己的 DOM 版本并将其传递给用户代码。

但是请注意,即使这种模式也有缺点。最重要的是,它可能只会阻止直接访问全局范围。用户代码仍然可以通过简单地声明没有var 的变量来创建全局变量,并且恶意代码可能会使用诸如创建函数并使用它的this 指针访问全局范围(JavaScript 的默认行为)之类的技巧。

那么让我们看一些代码:http://jsfiddle.net/C3Kw7/

【讨论】:

  • 对于一个完整的沙箱,您需要创建一个扫描仪,该扫描仪将用户代码中声明或定义的每个函数都放入沙箱中。一个简单的lexer 应该可以完成这项工作。您还需要match balanced brackets
  • 我希望对代码进行评估,以便能够访问当前窗口和 DOM。我不想要的是能够改变窗口或 DOM 的代码。
  • 如果不创建自己的 windowdocument 对象,就无法做到这一点。此外,自定义 document 对象上的自定义 getElementById 方法必须返回自定义 HTMLElement 对象,以防止插件开发人员能够修改 DOM。本质上,您需要创建自己的 DOM,这是一项艰巨的任务。你最好让开发人员制作插件然后验证它们。我敢肯定,你不会发现任何人一心想搞破坏。坦率地说,我认为你在太小的问题上浪费了太多时间。
  • 这个问题差不多两周了;实际上,我已经离开了它。只是想知道是否有一种相当简单的方法可以让执行的代码访问 getter,而不是 setter,而无需克隆 DOM/Window 对象。想了想,才发现收益太少,成本太高。然后昨天我想我会跟进回复:)
【解决方案2】:

如果您只想要一个简单的 getter,请编写一个而不是尝试评估任何东西。

function get(input) {
    // if input is a string, it will be used as a DOM selector
    // if it is an array (of strings), they will access properties of the global object
    if (typeof input == "string")
        return document.querySelector(input);
    else if (Array.isArray(input)) {
        var res = window;
        for (var i=0; i<input.length && typeof input[i] == "string" && res; i++)
            res = res[input[i]];
        return res;
    }
    return null;
}

【讨论】:

  • 这不是一个简单的文档查询。我希望任何 javascript 都可以访问/可执行,但我不希望该代码更改窗口对象。像safeEval('8*9') 这样的东西是有效的。
  • 那是不可能的。您想允许document.getElementById("x").getAttribute("href"),但阻止document.getElementById("x").removeAttribute("href")
【解决方案3】:

你可以这样做:http://jsfiddle.net/g68NP/

问题是您必须添加大量代码来保护每个属性、每个本机方法等。代码的实质归结为使用__defineGetter__,它的支持是有限的。由于您可能没有在 IE 上运行它,所以应该没问题。

编辑:http://jsfiddle.net/g68NP/1/ 此代码将使所有属性变为只读。使用hasOwnProperty() 可能合适,也可能不合适。

万一 JSFiddle 宕机:

function safeEval(input) {
    // Remove eval and evalJS from the window:
    var e = [window.eval, window.evalJS, document.getElementById], a;
    window.eval = function(){};
    window.evalJS = function(){};
    document.getElementById = function (id) {
        var elem = (e[2]).call(document, id);
        for (var prop in elem) {
            if (elem.hasOwnProperty(prop)) {
                elem.__defineGetter__(prop, function () {
                    return (function (val) {
                        return val;
                    }(elem[prop]));
                });
            }                
        }
        return elem;
    };

    try {
        /* More sanition needed before being passed to eval */

        // Eval the input, stuffed into an annonomous function
        // so the code to be evalued can not access the stored
        // eval functions:
        a = (e[0])("(function(){return " + input + "}())");
    } catch(ex){}

    // Return eval and evalJS to the window:
    window.eval = e[0];
    window.evalJS = e[1];
    document.getElementById = e[2];

    // Return the eval'd result
    return a;
}

【讨论】:

  • 这不是一个简单的文档查询。我希望任何 javascript 都可以访问/可执行,但我不希望该代码更改窗口对象。像safeEval('8*9') 这样的东西是有效的。
  • @SReject 我的 safeEval 中有什么 JS 代码无效? safeEval('8*9') 返回 72 就好了。除了将属性的值更改为由 document.getElementById 检索的元素之外,其他一切都可以正常工作。
  • @John 删除 eval() 有什么意义?很容易重新实现。此外,如果这是 FF,您可能想要隐藏组件,因为您可以使用 Components.lookupMethod(document, 'getElementById') 来获取原始实现。还可能值得在您的函数字符串插值中添加“使用严格”,并将正在运行的函数的 proto 设置为类似 (x={},x.__proto__=null,return x)。这对 FF 没有帮助,但肯定会阻止 chrome 中的一些顽皮的事情。
  • @JoeV 删除 eval() 的目的是为了满足“我还想让 eval 和 evalJS 无法从沙箱中访问”的要求。不过同意你的所有观点。
【解决方案4】:

我知道这是一篇旧帖子,但我只想分享 Aadit M Shah 解决方案的升级版本,这似乎真的是沙盒,没有任何方式访问窗口(或窗口子项) :http://jsfiddle.net/C3Kw7/20/

// create our own local versions of window and document with limited functionality

var locals = {
    window: {
    },
    document: {
    }
};

var that = Object.create(null); // create our own this object for the user code
var code = document.querySelector("textarea").value; // get the user code
var sandbox = createSandbox(code, that, locals); // create a sandbox

sandbox(); // call the user code in the sandbox

function createSandbox(code, that, locals) {
    code = '"use strict";' + code;
    var params = []; // the names of local variables
    var args = []; // the local variables

    var keys = Object.getOwnPropertyNames( window ),
    value;

    for( var i = 0; i < keys.length; ++i ) {
        //console.log(keys[i]);
        locals[keys[i]] = null;  
    }

    delete locals['eval'];
    delete locals['arguments'];

    locals['alert'] = window.alert; // enable alert to be used

    for (var param in locals) {
        if (locals.hasOwnProperty(param)) {
            args.push(locals[param]);
            params.push(param);
        }
    }

    var context = Array.prototype.concat.call(that, params, code); // create the parameter list for the sandbox
    //console.log(context);
    var sandbox = new (Function.prototype.bind.apply(Function, context)); // create the sandbox function
    context = Array.prototype.concat.call(that, args); // create the argument list for the sandbox

    return Function.prototype.bind.apply(sandbox, context); // bind the local variables to the sandbox
}

【讨论】:

  • 请注意,这是不安全的,可以通过这样做轻松绕过var global = Function("return this")(); global.alert(global.document);
  • @Anvaka 当我在这个沙箱中运行它时,我得到了Uncaught TypeError: Function is not a function
  • 这几乎可以完美运行,但如果我的页面有一个带有id="test" 的元素,那么代码可以通过简单的createSandbox('test', Object.create(null), {}) 访问该dom 节点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多