想象一下我想要不断调用用户提供的Javascript代码的场景,如下例所示,其中getUserResult
是某个用户(非我自己)编写的函数:
for (var i = 0; i < N; ++i) { var x = getUserResult(currentState); updateState(currentState, x); }
如何在浏览器和/或Node.js中执行这种代码,没有任何安全风险?
更一般地说,如何执行不允许修改甚至读取当前网页或任何其他全局状态的Javascript函数?是否有类似浏览器内的"JS虚拟机"?
JSFiddle如何确保您不能运行任何恶意代码(至少它可能会破坏您的登录名,在页面生命周期内运行机器人,如果不做更糟糕的事情)?或者它根本不确定?
经过深思熟虑并在这个帖子中的其他海报的帮助下(非常感谢你的帮助!),我找到了第一批问题的答案.我在这里重写我的答案,因为它总结了概念,并且还提供了一些实际的代码来进行实验.
通常,这个问题有两种解决方案:我们可以在隔离环境中使用iframe
或Worker
运行代码,从而无法读取或写入当前页面的信息(这是我的第一个主要安全问题).此外,有一些沙盒方法,如谷歌Caja,但(默认情况下)也运行其代码iframe
.
正如Juan Garcia所提出的,我将使用Web worker API,但这不是完整的故事.即使工作者无法直接从托管页面访问任何内容,仍然存在相当多的安全风险.此站点列出了Worker的上下文中可用的所有内置插件.
这个JSFiddle演示了一种在window
上下文之外运行一串代码的方法,而不必通过服务器,这仍然是不安全的,正如评论中所指出的那样.
我对此进行了扩展并采用了基于黑名单的方法,通过取消以下所有内容来禁用所有外部通信:
Worker
WebSocket
XMLHttpRequest
importScripts
不过,我目前正在将它移植到白名单的方式,如解释在这里,也使其安全的(近)的未来.
有关更多信息,请考虑:
W3Schools网络工作者教程.
这个线程和这个html5rocks教程有关如何运行Worker
没有单独的文件.
你可以和工人一起做.关于worker的最好的事情是它们在不同的进程中运行,因此如果该用户代码进入无限循环,则不会挂起您的页面.与worker相关的唯一接口是消息接口,因此只交换字符串,这是非常安全的,但在某些情况下是有限的.
由于并非所有当前使用的浏览器都支持工作人员,因此iframe是一种有用的选择 您可以在javascript中创建它们,将display设置为'none',将它们添加到文档中,从iframe contentWindow获取eval函数,然后'destroy'iframe,就像将outerHTML设置为''(空字符串)一样.这很棘手,因为有时候iframe窗口会收集垃圾,但如果你设法做得对,你就会得到一个eval,它绑定了一个不同的全局对象,它有一个空文档.然后,您可以使用运行它的代码创建一个接口,该代码全局,页面中的代码全局运行.安全性取决于您如何实现该接口.您不应公开全局对象,这意味着在运行任何用户代码之前,您应该取消iframe全局中的"父"属性.您还应该确保没有将任何Element对象从页面文档传递到iframe全局中运行的代码,或者运行该代码的用户将能够通过parentElement属性导航整个文档,您应该将它们包装到例如,使用Object.defineProperty的Element接口.它做了很多工作,它只做了一次,但不确定浏览器之间是否兼容.例如,如果我记得并且我没有犯错,Chrome让我取消iframe全局中的父属性,而在iPad上的Safari我无法做到.抱歉,我现在没有任何代码可以与您分享,如果我有时间,我会发布.但是,iframes脚本在与页面相同的进程中运行,这意味着无限循环将挂起您的页面脚本,今天浏览器将代码编译为机器指令可以有效地挂起客户端操作系统(取决于浏览器和OS).
第三,你可以使用沙箱,有TML Crowder评论编译器中的JavaScript自我解释器或类似的东西,这将简化为你,但副作用是它会运行较慢,特别是解释器.
恕我直言,我认为ECMA的人们应该更好地担心丑陋的箭头语法和类似的东西,并实现一种运行流程的安全方式,具有不同的全局,具有特定文档元素及其后代的接口,但无法获得该元素的父元素和访问该文档.这将有效地实现编写JavaScript插件的方式以及安全的广告系统.