以 root 身份运行的 Node.js REST 服务器

信息安全 linux 硬化 节点.js 特权分离
2021-08-23 14:07:00

我正在使用 Node.js 和 Express 构建一个 REST API 服务器,这将允许您将任何 Linux 服务器变成一个 NAS。我有几个fileExec基本上产生一个外壳和执行bash命令(像pvspvcreatelvsvgs,等等)。这些命令中的大多数都需要 root 访问权限才能正确执行。为此,我必须以 root 用户身份运行 Node.js,并使用sudo.

我知道我可能有一个非 root 用户运行服务器并在其中有一个单独的函数sudo来运行该特定命令。但是,我必须sudo在我的服务器应用程序中放置和管理密码才能获得正确的凭据。我不知道这样会不会好很多。此外,它会给我的应用程序增加更多复杂性......

所以我的问题是,有没有更好的方法来保护我的服务器应用程序?

4个回答

我建议将您的应用拆分为多个通过标准化 REST API 进行通信的组件。

在这种情况下,您将拥有一个存储管理器应用程序,该应用程序以具有足够权限的用户身份运行以管理 LVM 卷(或者如果不可能,则以 root 身份运行)并通过主应用程序(非特权)可以使用的 REST API 公开其服务. 出于性能和安全原因,那个小应用程序应该在 UNIX 套接字而不是 TCP 上进行监听(如果防火墙配置错误并且应用程序监听,则 UNIX 套接字将无法从 TCP 可能所在的任何地方访问)任何地址)。

这种方法还允许您在不修改主应用程序的情况下将低级组件与其他组件交换。

扩展安德烈的答案,这是我认为你应该接受的答案。

特权减少

第一步是确定应用工作流程所需的最低权限。据我了解,您需要操纵虚拟卷。您可以配置一个新的 UID 来执行此操作,而无需登录 shell 和其他root功能吗?大多数 Linux 界面都有用于授予权限的配置文件,因此请与开发人员确认您需要使用的命令。

如果可以创建单独的 UID

然后做一个。让你的应用程序那样运行。确保该 UID 没有 bash 和 home,因此如果应用程序被利用,则硬盘驱动器中不应有任何区域具有永久写入权限,尽可能。此外,没有机会在该 UID 上进行远程登录。您可以使用启动器脚本(使用 Upstart、Systemd、Init 等,而不是陷入这些争论)使用此帐户 ID 运行您的应用程序。

如果您必须使用根 UID

然后明白还有root比看起来更多的东西。首先,以 root 身份运行的进程可能会放弃一些被认为持有危险的特权。这些被称为Linux 功能因此,为您的应用程序编写一个包装器,以丢弃所有不相关的功能。如果您需要保留某些功能,请进行研究以确保它们不会被利用!例如,《利用 Linux 功能》讨论了如何重新获得仅从特定功能开始的完整功能集。

分隔

见安德烈的回答。基本上,让一个小型应用程序执行所有特权操作,以便 (1) 易于查看,您只需要验证 API 并执行正确的输入域验证;(2) 交付的代码更少,因此完全放弃对您的应用程序控制的可利用错误的机会更少。

硬化

在这种情况下旨在保护您的另一个武器是强制访问控制 Linux 安全模块。默认情况下,Linux 允许root覆盖对系统调用进行的大多数自由访问控制检查。但是,LSM 仍然可以在调用上应用它们自己的强制逻辑。这允许 LSM 减少root的权限。

我可以谈论 AppArmor 或 TOMOY Linux,但让我们把小学玩具留给蹒跚学步的孩子吧。您需要研究 SELinux,并真正理解它(该死。是的,这很难)。您需要创建一个 SELinux 角色,并将其应用于您的进程。在 SELinux 中,角色位于身份之上,以对所有系统调用应用策略。角色意味着您可以拥有一个root被禁止在系统上执行任何操作的用户。在这里,您需要识别您需要的系统调用(您可以audit2allow为此使用)并授予角色访问这些系统调用的权限。

获得权限列表后,请务必查看它们,并尽可能仅在适当的资源上允许它们。例如,可以允许角色在 /var/www 上写入文件,但不能在 /var/log 上写入文件。这意味着您可能需要使用特定类型标记系统上的某些文件,并编写自己的脚本来帮助您维护标签。您还需要为您的应用程序创建一个新域,以便您可以编写所谓的域和类型强制策略。该策略将规定您的应用程序在其域中如何只能与特定的其他类型和域进行交互。

总而言之,域类型策略将确保您的进程受到限制,而角色策略将确保您的 UID 受到限制。有一些重叠,但两者都是必要的,因为您可能会犯错误(或有正当需要)导致应用程序转移到另一个限制较少的域。您对应用施加的 SELinux 角色将确保只能向您选择的域进行转换。

您仍然有责任确保您的角色允许的所有域都具有足够低的权限,不会永久损害您的系统。如果 (1) 阻止任何“可利用的”Linux 功能,这很容易实现;(2) 受限应用与其他应用之间没有共享资源;(并且 (3) 您拒绝任何形式的永久存储(请参阅A Note on the Confinement Problem中的无记忆属性),因此受感染的应用程序不会持续存在)。

如果您无法做到这一点,那么您很可能无法保护您的系统,因为该应用程序只需要太多权限。那是时候聘请专家了,这样他们就可以检查您是否错过了什么!

假设程序没有缺陷,您可以安全地使用root凭据运行。不幸的是,情况并非总是如此,因此您永远不应该使用根凭据运行可公开访问的服务。

如果该服务不可公开访问(即仅限 LAN,或者您正在使用 IP 地址访问列表来限制访问者),那么您应该判断有人能够通过这些方式访问您的服务的可能性,以确定是否较低的安全性root服务是可以接受的。

否则,您需要将侦听器作为非root. 根据黑客和非root帐户设置,这很容易区分无损害黑客和完全+秘密接管。

一种选择是让父进程作为 运行root,然后让子进程执行主要应用程序功能,包括 HTTP 侦听器或openssl基于 - 的 HTTPS 侦听器。调用setuid以将其自己的凭据降低到noaccessnobodywebservd或您创建/认为适合此目的的任何帐户。用于stdin/stdout与父进程通信。

但实际上,我认为最简单的解决方案是在文件中设置无密码选项sudoers这只应针对运行您的服务的一个用户帐户(即webservd)执行,并且应具有特定的命令白名单。如果sudo为“任何”命令授予访问权限,那么您已经破坏了作为非运行的目的root

运行外部可访问的应用程序通常被认为是一个坏主意 root

正确的方法是:

  • 创建一个新用户,比如node-data只给它它需要的权限(也就是对 NAS 文件系统根目录的权限,而不是/对所需可执行文件的特定权限)

和/或

  • 创建 2 台节点服务器,一台面向外部,具有低级别用户访问权限,另一台通过 ZMQ 等专门用于运行 root 访问命令的内部 API。