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

170

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

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

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

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

也许解决方案是服务器处理用户定义的函数,然后生成要在客户端执行的回调? 即使该方法有效,是否有更好的方法来解决此问题?

15个回答

55
Google Caja是一种源码翻译器,它可以“允许您将不受信任的第三方HTML和JavaScript内联放置在您的页面中,并仍然保持安全性。”GitHub链接

6
快速测试表明,Caja 无法保护浏览器免受 CPU 攻击,例如“while (1) {}”——它只会挂起。同样地,“a=[]; while (1) { a=[a,a]; }”。 - David Given
6
好的,拒绝服务攻击不在范围之内:https://code.google.com/p/google-caja/issues/detail?id=1406 - Darius Bacon
5
该项目将于2021年1月31日被Google弃用。他们建议人们改用Closure库(https://github.com/google/closure-library)。 - Chris Owens
1
Closure刚刚宣布(2023/11/05),它也将在2024年8月1日被弃用。 - undefined

33

看一下道格拉斯·克罗克福德的ADsafe

ADsafe使得将客户端代码(如第三方脚本广告或小部件)放置在任何网页上都变得安全。ADsafe定义了一种JavaScript子集,它足够强大,可以允许客户端代码执行有价值的交互操作,同时防止恶意或意外的破坏或入侵。通过像JSLint这样的工具可以机械地验证ADsafe子集,因此不需要人工检查来审查客户端代码的安全性。ADsafe子集还实施良好的编码规范,增加了客户端代码正确运行的可能性。

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


1
在他们的网站上,我看不到使用ADsafe的方法。没有下载它的方式,没有代码链接,什么都没有。你怎么试用ADsafe呢? - B T
2
此外,它防止对this的任何访问,这是完全不可接受的。如果不使用this,就无法编写出良好的JavaScript代码。 - B T
11
@BT 我已经写过整个项目而没有使用 this。避免使用命名不当的参数并不难。 - soundly_typed
6
@BT 说完成真实世界的项目是不可接受的是愚蠢的。但是我很遗憾发起这个讨论,必须退出;这不是讨论此类事情的地方(抱歉)。如果你想进一步讨论,可以在推特上联系我。 - soundly_typed
3
当您在别人的环境中运行代码时,您会遇到规则和限制。我不会说这是不可接受的,可能有些麻烦,但并不是不能接受的。毕竟,对于每个使用this的情况,都有一个相等、等价的非this方式来实现它(毕竟它只是一个参数)。 - soundly_typed
显示剩余5条评论

23

我创建了一个名为jsandbox的沙盒库,它使用Web Workers来对评估的代码进行沙箱隔离。它还提供了一种输入方法,以显式地向沙盒代码提供其无法获得的数据。

以下是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
非常安全。请查看更新后的库在github上 - Eli Grey
1
这个项目还在维护吗?我看它已经超过两年没有更新了... - Yanick Rochon
2
嗨Eli - 感谢你提供了一个很棒的库,你打算维护它吗?我有一个更改请求,需要添加调试功能 - 从快速查看代码来看应该是可能的。请告诉我你的想法? - user1514042
1
@Rahly:任何允许操作DOM的东西,从定义上来说都是不安全的。你如何想象你会对DOM访问进行沙盒隔离? - Sasha Chedygov
3
理论上来说,您可以创建一个被监禁的文档片段,在该片段树中,任何操作/遍历都受到限制。 - Rahly
显示剩余3条评论

14

RyanOHara的Web Workers沙箱代码的基础上进行了改进,现在可以使用单个文件(不需要额外的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中测试)。


12

如其他回答所述,将代码困在带沙盒的iframe中(而不将其发送到服务器端)并使用消息进行通信即可。

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

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


9
我认为在这里提到js.js是值得的。它是用JavaScript编写的JavaScript解释器。
它比本地JavaScript慢约200倍,但其性质使其成为一个完美的沙盒环境。另一个缺点是其体积 - 几乎有600 KB,这在某些情况下可能对桌面设备可接受,但不适用于移动设备。

6
所有浏览器供应商和HTML5规范正在努力实现一个真正的沙盒属性,以允许沙盒化的iframe -- 但仍然限于iframe粒度。
总的来说,没有任何程度的正则表达式等可以安全地净化任意用户提供的JavaScript,因为它会退化为停机问题 :-/

2
你能解释一下它是如何退化成停机问题的吗? - hdgarrood
4
仅限于静态代码分析,解决停机问题的理论上不可能性并不适用于沙盒。沙盒可以执行诸如强制时间限制等操作,以应对停机问题。 - Aviendha

2
一种独立的JavaScript解释器更有可能产生一个强大的沙盒,而不是内置浏览器实现的受限版本。
Ryan已经提到了 js.js,但更为最新的项目是 JS-Interpreter。该文档涵盖了如何向解释器公开各种功能,但其范围在其他方面非常有限。

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


4
从这个代码中获取 window 非常简单。使用沙箱代码:sandboxcode('console.log((0,eval)("this"))') - Ry-
我必须想办法防止那种情况发生。 - alejandro
@alejandro 你找到防止这种情况的方法了吗? - Wilt
1
我的实现只是添加了以下内容: function sbx(s,p) {e = eval; eval = function(t){console.log("GOT GOOD")}; sandboxcode(s,p); eval =e} - YoniXw
4
@YoniXw:希望你最终没有用它做任何事情。像这样的方法永远不会奏效。(_=>_).constructor('return this')() - Ry-
显示剩余2条评论

0
截至2019年,vm2似乎是在Node.js中运行JavaScript的最受欢迎和最经常更新的解决方案。我不知道有前端解决方案。

2
vm2不支持在浏览器中运行时。但是,如果您想在nodejs应用程序中沙盒化代码,它应该可以工作。 - kevin.groat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接