在 Web 应用程序中对不受信任的用户提供的代码进行沙箱处理

信息安全 Web应用程序 沙盒
2021-08-10 10:00:38

我正在尝试创建一个编程游戏,其中用户提供的程序在战斗模拟中竞争,用作教授和练习编程的工具。(可能是回合制机器人模拟,但就本问题而言,它也可能是国际象棋或跳棋。)实现该游戏的一个主要组成部分是提供一种运行用户生成的机制针对游戏数据进行编码以确定其机器人的动作。

我知道典型的建议是“尽可能不要运行不受信任的代码”,而且我理解它的来源。不过,在我目前的情况下,如果可能的话,这将是我想要制作的应用程序的核心功能。我知道我需要采取一些预防措施来确保用户提供的代码不会造成损坏。据我所知,理想的设置将强制执行:

  • 用户代码从 STDIN 读取游戏状态
  • 用户代码写入生成的移动到 STDOUT
  • 用户代码与主机系统隔离
  • 用户代码相互隔离
  • 用户代码可以消耗的资源(CPU、内存、磁盘)是有限的
  • 用户代码无法访问网络

我的用例似乎并不独特。如果有的话,我想以下任何类型的应用程序都有类似的要求:

  • 大多数编程游戏
  • 竞技编程在线评委
  • “尝试 X 编程语言”
  • 游戏AI比赛

然而,我在 Google 上环顾四周,但找不到任何看起来值得信赖的参考实现。大多数上述类型的应用程序都是闭源的,也许是有充分理由的。

鉴于这些要求,我想我需要某种隔离/虚拟化/容器化解决方案,尽管老实说我不确定哪一个会提供必要的保证。

当前针对用户提供的代码进行沙盒处理的最佳实践是什么?有没有人有一些可靠来源的信息或参考资料?

4个回答

有点延迟地回到这个问题......我假设您收到的代码由客户端在他们的Javascript解释器中执行,并在某些时候在服务器上提交和解释以进行验证。

您有多个问题:

  1. 你想确保为一个玩家执行的代码不会对另一个玩家产生负面影响
  2. 您想确保您执行的代码不允许任何操作系统级别的权限提升
  3. 可选奖励:您想知道客户端代码执行期间何时出现问题

清理输入

首先要做的事情是:记住将您从客户那里获得的输入列入白名单。它们必须具有已知的长度和格式。使用与平台无关的格式来存储您收到的数据,该格式指定所有交换变量的长度和类型。

隔离客户

然后,您必须确保任何输入的计算都可以导致正确的结果,或者导致计算失败而不影响操作系统或其他并发计算。这意味着每个输入都在其自己包含的线程/进程中进行处理。您可以让您的 Web 服务器将输入转发到自定义守护程序,该守护程序为每个客户端生成一个沙盒进程,并将输入提供给它。

您还可以使用Wedge 之类的东西直接安全地划分单个服务器。辣椒也是一种选择。

即使您选择使用单个 Javascript 解释器来运行由不受信任的客户端编写的 JS 代码,您很快就会拥有适当的工具来保证隔离,因为COWL(一种成功实现 JS 代码不干扰的限制机制)正在标准化由 W3C 提供。

保护操作系统

无论您采用哪种方式,您只需要确保运行您的代码的进程是:

  • 不是由 root 运行 / 具有 root 等效功能
  • 包含在 cgroup 中以启用对 CPU、内存、磁盘和网络带宽的 QoS 限制(我认为后者可能需要网络命名空间)
  • 包含在用户命名空间中

知道计算结果是可信的

参加基于语言的安全课程 :-) 这通常是一个非常艰巨的目标,并且需要对语言和要保证的安全属性进行大量假设。

祝你好运!

由于大多数(超过 50%)的 Web 应用程序是用 Java 构建的,我假设您要部署一个基于 Java 的 Web 应用程序。

您可以从参与的学生那里获取 jar 文件,并首先针对众所周知的恶意软件对它们进行清理,以消除最明显的问题来源。

您可以定义一个接口类,所有提交的代码都应在其上实例化。这可能会限制暴露给游戏环境的一组函数,例如 - getGameState()、calculateMove() 等。游戏引擎将按照游戏规则以所需的顺序为每个参与者执行这些方法。

您应该通过定义具有特定 ClassLoader 的自定义 SecurityManager 来限制代码,以限制参与者安全域中的操作。这将使您能够以非常灵活的方式实施自定义安全策略。

您可以考虑应用以下访问权限:

  1. 禁用对文件系统的所有访问。
  2. 只允许反射访问它自己的类。
  3. 拒绝危险的系统调用,例如 load()、loadLibrary()、gc()、setSecurityManager()、console() 等。
  4. 禁用所有网络访问。
  5. 禁用创建新线程。

此外,考虑监控所执行代码的内存分配和处理器使用情况。您可以将其扩展到应用服务器上的任何和所有可用资源。

为了阻止顽皮或粗心的学生,您可以为他们的提交发布内存消耗和处理器利用率等指标。您还可以通过降低排名来惩罚大量资源消耗者,从而鼓励学生在使用计算资源时更加谨慎和经济。

你考虑过使用 JavaScript 和Google Caja吗?

Caja 编译器是一个工具,可以让第三方 HTML、CSS 和 JavaScript 安全地嵌入到您的网站中。它支持嵌入页面和嵌入应用程序之间的丰富交互。Caja 使用对象能力安全模型来支持广泛的灵活安全策略,以便您的网站可以有效地控制嵌入式第三方代码可以对用户数据执行的操作。

Caja 编译器支持大多数 HTML 和 CSS 以及最近标准化的 JavaScript 的“严格模式”JavaScript 版本——即使在不支持严格模式的旧浏览器上也是如此。它允许第三方代码在不支持它们的旧浏览器上使用新的 JavaScript 功能。

所以很多人都在说清理输入(这里的答案很好),但一个很好的方法是白名单/黑名单进行字符串匹配


黑名单:
如果字符串的任何部分包含被称为错误的内容,并根据可以远程更新的列表进行检查(例如,错误命令中已知字符串的 json,可以从存储库更新并随着时间的推移添加) ,那么您可以与之匹配,如果它符合安全要求,请继续运行它。


白名单:
这甚至带来了使用字符串匹配来调用您自己编写的预定义例程的附加功能,以创建您自己的简化指令集供玩家使用,这样他们甚至不能尝试做任何坏事。现在您所要做的就是扫描并查看它只包含您预先批准的模式(再次可以从某种存储库远程更新),如果它包含其他任何内容,只需发回一条错误代码即可。


缺点:

  • 及时维护清单
  • 不要让它太简单
  • 大部分时间和精力将花在处理命令上

好处:

  • 你定义了它
  • 您可以将输入发送到另一台机器上进行清理(如果机器响应正确、预格式化的方式[显式字符计数、数据包大小和模式],请信任输入并运行它)
  • 你可以让游戏变得更简单

因此,只要您保持系统安全,字符串匹配就可以轻松保持安全,因为您已经在这样做来清理输入。