如何在服务器端运行不受信任的代码?

33

我试图在Linux + Node.js中使用沙盒模块来运行不受信任的JavaScript代码,但它无法正常工作。我只需要让用户编写打印一些文本的JavaScript程序。不允许其他输入/输出,并且仅使用纯JavaScript语言,不能使用其他的Node.js模块。 如果这样做实在不可能,您建议使用哪种语言来完成此类任务?我需要的最小功能集包括一些数学、正则表达式、字符串操作和基本的JSON函数。 脚本最多可以运行5秒钟,然后进程将被终止,我该如何实现?


2
你可以使用Ruby。我可以帮你将其沙盒化。它具有所有功能(正则表达式、数学、字符串和JSON库)。你也可以始终使用更低级别的沙盒化思想:要么使用普通权限,要么使用SELinux(但这似乎过于严格了)。 - Linuxios
这是一个基本的Gist:https://gist.github.com/2890984,我刚写的。当Ruby的全局`$SAFE`变量设置为4(最高级别)时,除了允许的内容外,它将阻止几乎所有其他操作。它将禁止I/O、网络、大多数对其未创建的其他对象的访问等。然后我们可以安全地使用可怕的`eval`。`Thread`部分是因为除非您在另一个线程中进行沙箱处理,否则您的主线程将受到`$SAFE`级别4的相同限制。 - Linuxios
3
“Sandbox模块损坏”是什么意思?您确定指的是这个模块:http://gf3.github.com/sandbox/吗? - alessioalex
该模块的行为不稳定,我想要一个更稳定的版本或替代方案。 - AlfredoVR
3
你能否说明那个模块的行为异常?如果你能够发布你目前在使用的JS代码,或许会有所帮助。 - halfer
显示剩余3条评论
10个回答

18

我见过在此类问题中提到的所有库(vm2jailed)都试图隔离node进程本身。这些类型的“监狱”不断被攻破,并且高度依赖于node标准库的未来升级,以避免暴露其他攻击向量。

另一种选择是直接使用V8 :: Isolate类。它旨在隔离Google Chrome和node中的JavaScript,因此您可以期望它得到充分维护,并比您、我或单个库维护者更加安全。
该类仅能运行“纯”JavaScript。它具有完整的ECMAScript实现,但没有浏览器API或node API。
这是Cloudflare为其Worker产品使用的内容。

deno,由node的创建者开发的新语言,采用默认沙箱化的雄心壮志,使用完全相同的方法并根据启用的标志公开标准库的部分内容。

node 环境中,你可以使用isolated-vm库。它是一个非常棒的库,可以创建与要在隔离中运行的代码分离的v8::Isolate子进程。
它提供了将值和函数传递到隔离区域和返回的方法。虽然这不像大多数“监狱”库那样易于使用,但可保证 JavaScript 代码的真正沙盒化。
由于它是“纯”JavaScript,所以唯一的逃逸口是你以注入函数形式提供的逃脱口。
此外,随着每个node版本的更新,它会自动更新,因为它使用了node自己的v8::Isolate
其中一个主要的痛点是,如果你想向你的脚本中注入库,你可能需要使用打包工具如webpack来将所有东西打包在一个单独的脚本中,让库能够使用它。
我个人使用它来执行用户提供的代码,在网络爬虫中从网页中提取信息,并且效果非常好。

这是Cloudflare使用的东西...你是指 V8::Isolate 吗?如果是的话,那么段落间距应该被修复。 - collimarco
调整过了,但在我看来之前读起来很好。 :-) - Jerska

9

我最近为沙盒化不受信任的代码创建了一个库,它似乎符合要求(在Node.js中以受限进程执行代码,在Web浏览器中使用沙盒内的iframe中的Worker执行):

https://github.com/asvd/jailed

有机会将主应用程序中给定的一组方法导出到沙盒中,从而提供任何自定义API和一组权限(实际上,这就是我决定从头开始制作库的原因)。所提到的数学、正则表达式和字符串相关内容由JavaScript本身提供,任何其他内容都可以明确地从外部导出(例如用于与主应用程序通信的某些函数)。


4

谢谢分享!我将你提到的要点打包成了一个 Ruby gem,链接在这里:https://github.com/vaharoni/trusted-sandbox。它可以让 Ruby 运行不受信任的代码,也可以通过使用 Ruby Racer 轻松地运行 JS。它允许设置磁盘配额、限制内存、共享 CPU 等。 - AmitA
6
Docker并不是一个用于隔离的工具,而是旨在简化应用程序的“打包”和分发。这并不意味着它完全不安全(例如请参见此处),但安全性并不是其设计原则的一部分,因此将其表示为“沙箱工具”是不正确的。据我所知,隔离不受信任代码的标准方式是使用虚拟机或 - 对于支持它的解释型语言(例如:pypy)- 使用解释器的“沙箱选项”... - mac
这在2018年仍然正确吗? - Yahya Uddin
2
这个答案应该被删除以避免混淆,容器已经被定义为不适合作为任何安全相关工具的隔离工具。 - Vicary

3
沙盒的基本思想是,你需要预先定义全局变量来完成操作,因此如果你通过取消设置或用受控制的变量替换它们来拒绝脚本,它就无法逃脱。只要你不遗漏任何内容。
首先替换deny require()或用受控制的东西替换它。 不要忘记进程和“全局”即“根”,困难的事情是不要遗漏任何东西,这就是为什么依赖于其他人构建沙箱是一个好主意的原因;-)

3

很晚了,回答这个问题可能有点晚,我想下面的工具可能是一个有价值的补充,以上回答/评论中没有提到。

我正在尝试实现类似的用例。在查阅了网络资源后,https://www.npmjs.com/package/vm2 似乎很好地处理了沙盒环境(nodejs)。

它基本上满足了沙盒特性,如限制对内置或外部模块的访问、沙盒之间的数据交换等。


1
如果您可以承受性能损失,您可以在适当的CPU和内存限制下运行JS虚拟机。当然,这样做就要信任VM解决方案的安全性。将其与普通JS沙箱一起使用,您将拥有两个安全层。为了增加一层保护,将沙箱放在与主应用程序不同的物理机器上。

0

虽然回答晚了,但也许是一个有趣的想法。

静态代码分析 => AST 操作 => 代码生成

  1. 静态分析将解析源代码的 AST。AST 提供了一种通用的数据结构,使我们能够遍历和修改源代码。
  2. 通过 AST 操作,我们可以找出外部作用域中所有敏感变量的标识符引用。如果需要,我们可以在函数体的开头重新声明和初始化它们,以便覆盖它们。因此,从内部到外部的引用都在控制之中。
  3. 从 AST 生成代码也很容易。

例如,一个函数如下所示:

function () {
    a = 1;
    window.b = 1;
    eval('window.c()');
}

基于JS代码解析器的静态分析使我们能够在原始函数体之前插入变量声明语句:
function () {
    var a, window = {}, eval = function () {}; // variable overwriting
    a = 1;
    window.b = 1;
    eval('window.c()');
}

就这样。

应考虑更多的覆写,例如eval()new Function()和其他全局对象或API。在解析过程中应该对警告进行良好的组织和报告。

一些相关工作的顺序:

  • esprima,用于多功能分析的ECMAScript解析基础设施。
  • estraverse,ECMAScript JS AST遍历函数。
  • escope,ECMAScript范围分析器。
  • escodegen,ECMAScript代码生成器。

我基于以上做法的实践是function-sandbox


0

我现在也面临着类似的问题,而且我只看到了关于沙盒模块的负面评价。

如果您不需要任何特定于节点环境的内容,我认为最好的方法是使用无头浏览器,例如PhantomJS或Chimera作为沙盒环境。


0
我们在处理其中一个产品时遇到了同样的问题。我们希望允许用户提供他们自己的自定义(不受信任)代码,在特定的关键事件中运行,例如任务完成。这基本上是Webhooks的更好替代方案!
最终我们构建了一个单独的服务,使用AWS Lambda、Rust和V8::Isolate的组合以及其他一些技术。这不仅使得安全性得到保障,而且速度也非常快。我们还添加了自己的fetch()等集成,因为V8不支持Web或Node特定的APIs。这进一步允许我们做一些很棒的事情,比如限制脚本可以通信的端点,甚至通过注入预配置的Authorization标头来预认证请求/域名。
与其公开源代码,我们选择将该服务作为托管服务提供给其他人。该服务已全球部署,无需设置,并且默认情况下完全无状态!您可以在https://scriptable.run上查看。

-6

问问自己以下问题:

  1. 你是地球上最聪明的人之一吗?
  2. 你经常拒绝谷歌、Mozilla和卡巴斯基实验室的工作邀请,因为这会让你感到无聊吗?
  3. “不受信任的代码”是来自与你同公司的人还是来自全球各地的犯罪分子和无聊的电脑小孩?
  4. 你确定node.js没有安全漏洞会泄露到你的沙盒中吗?
  5. 你能写出完美无误的100%错误免费的代码吗?
  6. 你对JavaScript了解得很透彻吗?

正如你通过sandbox module的实验已经知道的那样,编写自己的沙盒并不容易。沙盒的主要问题在于你必须做到万无一失。一个错误将完全破坏你的安全性,这就是为什么浏览器开发人员与全球的黑客进行持续战斗。

话虽如此,简单的沙盒还是相当容易实现的。首先,你需要编写自己的JavaScript解释器,因为你不能使用node.js的解释器,因为它包含eval()require()(这两个函数都会让黑客逃离你的沙盒)。

解释器必须确保被解释的代码不能访问除你提供的少量全局符号以外的任何内容。这意味着不能有eval()函数,例如(或者你必须确保该函数只在你自己的JavaScript解释器的上下文中被评估)。

这种方法的缺点是:需要大量工作,如果在解释器中犯了一个错误,黑客可以离开沙盒。

另一种方法是清理代码并使用node.js的eval()运行它。您可以通过运行一堆正则表达式来清理现有代码,例如/eval\s*[(]//g以删除恶意代码部分。

这种方法的缺点是:很容易犯一个错误,使您容易受到攻击。例如,可能会存在正则表达式和node.js之间的不匹配,导致它们对“空格”的理解不同。一些晦涩的Unicode空格可能会被解释器接受,但不会被正则表达式接受,这将允许攻击者运行eval()

我的建议是:编写一个小型演示测试用例,展示沙盒模块如何被破坏,并进行修复。这将节省您大量的时间和精力,如果沙盒中存在漏洞,那么这不会完全是您的错。


3
eval = null; require = null;从这里开始不能再运行这两个函数了。我不会说创建一个沙盒很容易,因为你是对的,如果忘记了一些东西(比如隐式的setTimers),那么就会出问题!但是你有点过于复杂化了。 - axkibe
当然。问题在于OP寻找一种便宜的解决方案,而这根本不存在。安全始终是昂贵和麻烦的。你的解决方案看起来很好,直到一个客户出现并要求能够调用eval()。一些“聪明人”通过添加var __ev = eval; eval = null;来“修复”问题,因为“没有人会发现这个问题”(安全性靠混淆)。 - Aaron Digulla
6
为什么eval实际上是不好的?如果将eval执行的代码设为null或undefined,它无法访问require和process。所以...我不明白为什么你认为必须预先检查代码才能在沙盒中运行。沙盒只需对其可以访问的环境进行严格限制即可。 - axkibe
8
你是不是地球上最聪明的人之一?这些问题没有必要贬低别人,并且对你的回答没有任何帮助。你不需要成为天才来找到在服务器端运行外部代码的方法(你可能会注意到很多人都运行Web主机服务)。 - Lodewijk
此外,安全并不是“昂贵”的,而且很少会对性能产生影响。它是精确的,而不是困难的。 - Lodewijk

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