158

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

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

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

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

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

4

15 回答 15

54

Google Caja is a source-to-source translator that "allows you to put untrusted third-party HTML and JavaScript inline in your page and still be secure."

于 2008-10-12T07:35:28.843 回答
33

看看Douglas Crockford 的 ADsafe

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

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

于 2008-10-12T20:56:33.547 回答
23

我创建了一个名为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
      }
  });
于 2009-06-21T20:39:46.457 回答
11

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 中测试)。

于 2016-05-11T06:34:16.517 回答
9

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

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

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

于 2014-09-18T15:10:02.533 回答
8

我认为js.js在这里值得一提。它是用 JavaScript 编写的 JavaScript 解释器。

它比原生 JavaScript 慢大约 200 倍,但它的本质使它成为一个完美的沙盒环境。另一个缺点是它的大小 - 几乎 600 KB,在某些情况下对于台式机来说可能是可以接受的,但对于移动设备来说则不行。

于 2014-07-03T20:01:48.640 回答
6

All the browser vendors and the HTML5 specification are working towards an actual sandbox property to allow sandboxed iframes -- but it's still limited to iframe granularity.

In general, no degree of regular expressions, etc. can safely sanitise arbitrary user provided JavaScript as it degenerates to the halting problem :-/

于 2008-10-12T07:25:44.043 回答
2

与内置浏览器实现的封闭版本相比,独立的 JavaScript 解释器更有可能产生健壮的沙箱。

Ryan已经提到 过 js.js,但更新的项目是JS-Interpreter该文档涵盖了如何向解释器公开各种功能,但其范围非常有限。

于 2016-08-11T13:44:30.017 回答
1

一种丑陋的方式,但也许这对你有用:

我获取了所有全局变量并在沙盒范围内重新定义了它们,同时我添加了严格模式,因此它们无法使用匿名函数获取全局对象。

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

于 2014-03-06T03:39:32.943 回答
0

截至 2019 年,vm2看起来是在 Node.js中运行 JavaScript 的最流行和最经常更新的解决方案。我不知道前端解决方案。

于 2019-08-07T09:46:08.013 回答
0

使用NISP,您将能够进行沙盒评估。

尽管您编写的表达式并不完全是 JavaScript 代码,但您将编写S-expressions。它非常适合不需要大量编程的简单DSL 。

于 2020-05-20T14:44:11.827 回答
-3
  1. Suppose you have code to execute:

     var sCode = "alert(document)";
    

    Now, suppose you want to execute it in a sandbox:

     new Function("window", "with(window){" + sCode + "}")({});
    

    These two lines when executed will fail, because "alert" function is not available from the "sandbox"

  2. And now you want to expose a member of window object with your functionality:

     new Function("window", "with(window){" + sCode + "}")({
         'alert':function(sString){document.title = sString}
     });
    

Indeed you can add quotes escaping and make other polishing, but I guess the idea is clear.

于 2008-10-12T09:32:36.220 回答
-4

这个用户 JavaScript 代码来自哪里?

对于用户将代码嵌入到您的页面然后从他们的浏览器调用它(请参阅Greasemonkey),您无能为力。这只是浏览器所做的事情。

但是,如果您将脚本存储在数据库中,然后检索它并对其进行 eval(),那么您可以在脚本运行之前对其进行清理。

删除所有窗口的代码示例。和文件。参考:

 eval(
  unsafeUserScript
    .replace(/\/\/.+\n|\/\*.*\*\/, '') // Clear all comments
    .replace(/\s(window|document)\s*[\;\)\.]/, '') // Removes window. Or window; or window)
 )

这试图阻止执行以下操作(未测试):

window.location = 'http://example.com';
var w = window;

您必须对不安全的用户脚本应用很多限制。不幸的是,没有任何可用于 JavaScript 的“沙盒容器”。

于 2008-10-12T06:44:04.690 回答
-5

我一直在研究一个简单的 JavaScript 沙箱,让用户为我的网站构建小程序。尽管在允许 DOM 访问方面我仍然面临一些挑战(parentNode 只是不会让我保持安全 =/),但我的方法只是用一些有用/无害的成员重新定义 window 对象,然后 eval() 用户将此重新定义的窗口作为默认范围的代码。

我的“核心”代码是这样的......(我没有完全展示它;)

function Sandbox(parent){

    this.scope = {
        window: {
            alert: function(str){
                alert("Overriden Alert: " + str);
            },
            prompt: function(message, defaultValue){
                return prompt("Overriden Prompt:" + message, defaultValue);
            },
            document: null,
            .
            .
            .
            .
        }
    };

    this.execute = function(codestring){

        // Here some code sanitizing, please

        with (this.scope) {
            with (window) {
                eval(codestring);
            }
        }
    };
}

因此,我可以实例化一个沙盒并使用它的execute()函数来运行代码。此外,eval 代码中所有新声明的变量最终都将绑定到 execute() 范围,因此不会出现名称冲突或与现有代码混淆。

尽管全局对象仍然可以访问,但那些对沙盒代码仍然未知的对象必须在 Sandbox::scope 对象中定义为代理。

于 2009-05-20T14:10:09.137 回答
-6

您可以将用户的代码包装在一个函数中,该函数将禁止对象重新定义为参数——然后undefined在调用这些对象时:

(function (alert) {

alert ("uh oh!"); // User code

}) ();

当然,聪明的攻击者可以通过检查 JavaScript DOM 并找到一个包含对窗口的引用的非覆盖对象来绕过这个问题。


另一个想法是使用JSLint之类的工具扫描用户的代码。确保将其设置为没有预设变量(或:只有您想要的变量),然后如果设置或访问了任何全局变量,则不要使用用户的脚本。同样,它可能容易受到 DOM 遍历的影响——用户可以使用字面量构造的对象可能具有对可以访问以逃避沙箱的 window 对象的隐式引用。

于 2008-10-12T06:26:01.213 回答