沙盒JavaScript是否可以在浏览器中运行?


142

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

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

在最简单的情况下,假设我要阻止用户致电alert。我可以想到的几种方法是:

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

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

Answers:



32

看看Douglas Crockford的ADsafe

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

通过查看项目GitHub存储库中的template.htmltemplate.js文件,可以看到有关如何使用ADsafe的示例。


在他们的网站上,我看不到使用ADsafe的任何方法。没有下载方法,没有代码链接,什么也没有。您如何尝试ADsafe?
英国电信

2
此外,它还阻止了对的任何访问this,这是完全不可接受的。不使用,就无法编写优质的javascript this
英国电信

4
@BT我没有使用编写了整个项目this。不难避免使用命名错误的参数。
2011年

2
@BT说完成现实世界的项目是不可接受的。但是我很遗憾开始讨论,因此必须退出。这里不是讨论这类事情的地方(对不起)。如果您想进一步讨论,我在Twitter上。
soundly_typed

1
@BT(我会继续讨论,因为它与问题有关。)只要您在别人的环境中运行代码,就会遇到规则和限制。我不会认为这是不能接受的。也许是“痛苦中的屁股”。但并非不可接受。毕竟,对于的每次使用this,都有一个相等的,等效的非this方法(毕竟,这只是一个参数)。
2011年

24

我创建了一个名为jsandbox的沙箱库,该库使用Web Worker对评估的代码进行沙箱处理。它还具有一种输入方法,用于显式提供原本无法获取的沙盒代码数据。

以下是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:这看起来真的很酷。这样执行用户代码的安全性如何?
康斯坦丁·塔尔库斯

1
非常安全。在github上检查更新的库。
伊莱·格雷

1
这个项目还在维护吗?我看到它已经超过2年没有更新了……
Yanick Rochon 2012年

我喜欢这样,除了如果要沙箱但仍允许代码访问说jQuery,这将失败,因为网络工作者不允许DOM操作。
拉赫利2015年

嗨,Eli-感谢您提供伟大的lib,您打算维护它吗?我有一个更改请求,要求添加调试功能-通过快速查看代码应该可以实现。请让我知道你的想法?
user1514042 2015年

8

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

它比本地JS慢200倍,但是它的性质使其成为一个完美的沙箱环境。另一个缺点是它的大小-近600 kb,在某些情况下对于台式机是可以接受的,但对于移动设备则不可接受。


7

正如其他回复中所提到的,将代码监禁在沙盒iframe中(无需将其发送到服务器端)并与消息进行通信就足够了。我建议看一下我创建的一个小型库,主要是因为需要向不受信任的代码提供一些API,就像问题中所述的那样:有机会将特定的一组函数直接导出到沙箱中,不受信任的代码运行。还有一个演示,它执行用户在沙箱中提交的代码:

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


4

所有浏览器供应商和HTML5规范都在努力实现实际的沙箱属性,以允许使用沙箱iframe进行操作-但仍限于iframe粒度。

通常,任何程度的正则表达式等都无法安全地清理任意用户提供的JavaScript,因为它会退化为停止问题:-/


2
您能解释一下它如何退化为停顿问题吗?
hdgarrood13年

2
解决暂停问题的理论上的可能性实际上仅适用于静态代码分析。沙箱可以执行诸如强制时间限制之类的事情来处理停止问题。
Aviendha

4

@RyanOHara的Web worker沙箱代码的改进版本,位于单个文件中(无需额外的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中测试)。


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


3
从中获得window回报微不足道。sandboxcode('console.log((0,eval)("this"))')
Ry-

我必须弄清楚如何防止这种情况
alejandro

@alejandro您是否找到防止这种情况的方法?
威尔特

1
我的实现只是添加:function sbx(s,p) {e = eval; eval = function(t){console.log("GOT GOOD")}; sandboxcode(s,p); eval =e}
YoniXw

2
@YoniXw:希望您最终不要将它用于任何用途。这样的方法永远行不通。(_=>_).constructor('return this')()
Ry-


1

截至2019年,vm2似乎是针对此问题的最受欢迎且最经常更新的解决方案。


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


-3

1)假设您要执行以下代码:

var sCode = "alert(document)";

现在,假设您要在沙箱中执行它:

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

这两行在执行时将失败,因为“沙箱”中没有“警报”功能

2)现在,您要使用功能公开window对象的成员:

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

确实,您可以添加引号转义并进行其他修饰,但是我想这个主意很明确。


7
是否有无数其他方法可以到达全局对象?例如,在使用func.apply(null)调用的函数中,“ this”将成为窗口对象。
mbarkhau 2011年

5
第一个示例没有失败,这是一个非常无效的沙盒示例。
Andy E

1
var sCode =“ this.alert('FAIL')”;
伦纳德·保利

-4

该用户JavaScript来自何处?

用户将代码嵌入到页面中,然后从他们的浏览器中调用代码,您无能为力(请参阅Greasemonkey,http://www.greasespot.net/)。这只是浏览器所做的。

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

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

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

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

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

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


2
如果有人试图做一些恶意的事情,那么简单的正则表达式就做不到-采取(function(){this [“ loca” +“ tion”] =“ example.com ”;}}()无法信任您的用户(在任何可以随意添加内容的网站上都是如此),阻止所有js是必要的。
olliej

我过去使用过类似的工具。它不是完美的,但是可以帮助您实现大部分目标。
Sugendran

olliej,您对这种技术的局限性是正确的。如何覆盖全局变量,例如<code> var window = null,document = null,this = {}; </ code>?
Dimitry

Dimitry Z不允许[在某些浏览器中]覆盖这些变量。还要在答案列表中检查我的解决方案-可以。
谢尔盖·伊林斯基

-5

我一直在开发一个简化的js沙箱,让用户为我的网站构建applet。尽管我在允许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()来运行代码。同样,评估代码中所有新声明的变量最终都将绑定到execute()范围,因此不会出现名称冲突或与现有代码混淆的情况。

尽管全局对象仍然可以访问,但是对于沙盒代码仍不知道的全局对象,必须在Sandbox :: scope对象中将其定义为代理。

希望这对您有用。


8
这不会沙箱化任何东西。逃避的代码可以删除成员并以这种方式进入全局范围,或者通过执行(function(){return this;})()
Mike Samuel

-6

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

(function (alert) {

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

}) ();

当然,聪明的攻击者可以通过检查Javascript DOM并找到一个包含对窗口的引用的不可重写对象来解决此问题。


另一个想法是使用jslint之类的工具扫描用户的代码。确保将其设置为没有预设变量(或:仅包含您想要的变量),然后如果设置或访问了任何全局变量,请勿使用用户的脚本。同样,可能容易受到DOM的攻击-用户可以使用文字构造的对象可能具有对窗口对象的隐式引用,可以对该对象进行访问以避开沙箱。


2
如果用户输入window.alert而不是普通警报,则他们将绕过该限制。
昆汀

@Dorward:是的,因此是“禁止的对象”。wrunsby应该确定不允许用户访问哪些对象,并将它们放置在参数列表中。
约翰·米利金

只有一个对象-窗口。如果您不阻止对它的访问,那么一切都可以通过它获得。如果您阻止了该脚本,那么该脚本将无法访问其任何属性(因为说出alert而不是window.alert只是暗示了该窗口。)。
昆汀

@Doward:不会阻止window.alert,但是警报仍然可以起作用,请尝试一下。这是因为window也是全局对象。需要阻止窗口以及您不希望用户代码访问的窗口的任何属性或方法。
AnthonyWJones
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.