是否可以对在浏览器中运行的 JavaScript 进行沙箱处理?

IT技术 javascript browser sandbox
2021-01-13 18:14:44

我想知道是否有可能对在浏览器中运行的 JavaScript 进行沙箱处理,以防止访问在 HTML 页面中运行的 JavaScript 代码通常可用的功能。

例如,假设我想为最终用户提供一个 JavaScript API,让他们定义在“有趣的事件”发生时要运行的事件处理程序,但我不希望这些用户访问window对象的属性和函数我能做到吗?

在最简单的情况下,假设我想阻止用户调用alert. 我能想到的几种方法是:

  • 重新定义window.alert全局。我认为这不是一种有效的方法,因为在页面中运行的其他代码(即,不是由用户在其事件处理程序中创作的内容)可能想要使用alert.
  • 将事件处理程序代码发送到服务器进行处理。我不确定将代码发送到服务器进行处理是正确的方法,因为事件处理程序需要在页面的上下文中运行。

也许服务器处理用户定义的函数然后生成要在客户端上执行的回调的解决方案会起作用吗?即使这种方法有效,有没有更好的方法来解决这个问题?

6个回答

Google Caja是一个源到源的翻译器,它“允许您将不受信任的第三方 HTML 和 JavaScript 内嵌到您的页面中,并且仍然安全。”

快速测试表明 Caja 无法保护浏览器免受 CPU 攻击,例如while (1) {}--- 它只是挂起。同样a=[]; while (1) { a=[a,a]; }
2021-03-12 18:14:44
是的,拒绝服务超出范围:code.google.com/p/google-caja/issues/detail?id=1406
2021-03-14 18:14:44
Google 将于 2021 年 1 月 31 日弃用该项目。他们建议人们改用Closure 库 ( github.com/google/closure-library )。
2021-03-29 18:14:44

看看Douglas Crockford 的 ADsafe

ADsafe 可以安全地在任何网页上放置访客代码(例如第三方脚本广告或小部件)。ADsafe 定义了 JavaScript 的一个子集,该子集足够强大,允许访客代码执行有value的交互,同时防止恶意或意外损坏或入侵。ADsafe 子集可以通过 JSLint 等工具进行机械验证,因此无需人工检查即可检查访客代码的安全性。ADsafe 子集还强制执行良好的编码实践,增加来宾代码正确运行的可能性。

您可以通过查看项目的 GitHub 存储库中的template.htmltemplate.js文件来查看如何使用 ADsafe 的示例

此外,它阻止了对 的任何访问this,这是完全不可接受的。不使用 .js 就无法编写好的 javascript this
2021-03-18 18:14:44
@BT 我已经编写了整个项目而不使用this. 不难避免命名不当的参数。
2021-03-21 18:14:44
在他们的网站上,我看不到使用 ADsafe 的方法。没有办法下载它,没有代码链接,什么都没有。如何试用 ADsafe?
2021-03-31 18:14:44
@BT(我会继续,因为它与问题相关)无论何时您在其他人的环境中运行代码,您都会遇到规则和限制。我不会认为这是不可接受的。也许是“屁股痛”。但并非不能接受。毕竟,对于每次使用this,都有一个相等的、等效的非this方法来做到这一点(毕竟它只是一个参数)。
2021-04-05 18:14:44
@BT 说完成现实世界的项目是不可接受的,这是愚蠢的。但我很后悔开始这个讨论,必须退出;这里不是讨论这些事情的地方(抱歉)。如果你想进一步讨论,我在twitter上。
2021-04-08 18:14:44

我创建了一个名为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
      }
  });
这个项目还在维护吗?我看到它已经超过 2 年没有更新了......
2021-03-12 18:14:44
@Rahly:根据定义,任何允许操作 DOM 的东西都是不安全的。你如何想象你会沙箱 DOM 访问?
2021-03-14 18:14:44
理论上?您将创建一个被监禁的文档片段,其中任何操作/遍历都受片段树的限制。
2021-03-15 18:14:44
嗨 Eli - 感谢您提供的出色库,您打算维护它吗?我有一个添加调试功能的更改请求 - 通过快速查看代码应该是可能的。请让我知道你的想法?
2021-03-17 18:14:44
非常安全。在 github 上查看更新的库
2021-04-08 18:14:44

正如其他回复中提到的,将代码放入沙盒 iframe 中(无需将其发送到服务器端)并与消息进行通信就足够了。

我建议看看我创建的一个小库,主要是因为需要为不受信任的代码提供一些 API,就像问题中描述的那样:有机会将特定的函数集导出到沙箱中不受信任的代码运行。还有一个演示,它在沙箱中执行用户提交的代码:

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

RyanOHara 的网络工作者沙箱代码的改进版本,在单个文件中(不需要额外的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 中测试)。