让 PHP 执行 shell 脚本的正确且安全的方法

Correct and secure way to have PHP execute a shell script

我正在开发一个应用程序,人们(任何人)都可以上传代码(Java 并且可能首先是 C(++)),这些代码将被编译并 运行 在服务器上.这当然是一个巨大的安全风险,并且有必要将所有这些都正确地放在沙箱中。 虽然这个沙箱超出了这个问题的范围。假设已经处理好了。

除此之外,系统中还会有依赖shell命令和PHP的exec()、shell_exec()等函数的函数。需要执行的命令不是很多,主要是 java(c)、gcc、g++ 等。如果需要,列出我们需要的命令列表是相当容易的。一旦我们决定,用户将无法执行其他命令。比如有人上传了一些java代码,要求服务器编译。然后服务器会运行javac。用户的输入只能改变 javac 的参数(使用 escapeshellarg() 进行转义)。

我想知道我还应该采取哪些安全预防措施。我打算使用 PHP 的安全模式,这样只能执行 safe_mode_exec_dir 中的文件。我还计划将 shell 文件的所有权设置为 root:www-data,这样 www-data 就不能更改权限或所有权,而且还打算拥有类似 rwxr-xr-- 的权限,这样 www -data 不能修改文件。但是,从 5.4.0 开始,安全模式已从 PHP 中移除。目前做这样的事情的方法是什么?

让完全不同的用户使用这些 shell 命令 运行 会更安全吗?除了 safe_mode_exec_dir 甚至无法访问任何其他目录?那我该怎么做呢?

另一种选择是 PHP 只维护需要完成的事情的列表,并让受限用户每分钟左右执行一个 cronjob 运行 来完成列表并执行必要的操作。那会是更安全的方法吗?由于最多一分钟的延迟,我更愿意直接从 PHP 执行此操作。

我可以完全访问我的服务器,但由于政策原因,我不能使用虚拟化。

实际上,在给予人们这种自由时,唯一真正安全的步骤是为每个用户会话启动一个新的虚拟实例,并在会话关闭后立即 'burn' 它。 如果你想要某种持久性,cat 他们的输入,并在他们下次访问时 运行 它在一个新实例上。即使这有被利用的巨大空间,但对您系统的损害应该是有限的。

在我看来,获得编译和 运行 脚本的安全环境的一种好而简单的方法是 LXC 容器。

你说你不能使用虚拟化,但从技术上讲它只是进程隔离。就像类固醇的 chroot。我有一个本身就是虚拟机的容器主机,到目前为止我没有遇到任何问题。真的 LXC 不是虚拟化,应该适合你的需要。

这里有一些介绍链接:

例如,您可以为每个游戏创建一个模板容器,其中包含编译所需的所有库和 运行 上传的代码。此模板可以根据需要保护和限制 cpu、ram 和磁盘 IO。我认为使用 lxc.network.flag = downlxc.network.type = empty

关闭网络也是个好主意

然后,当上传代码时,您可以克隆模板容器,将代码放入其中并构建它,运行 代码。

所有这些都可以通过从 php 调用的 shell 脚本或通过一系列 php 系统调用来完成,但这听起来不太好。

对于您想做的事情,必须使用非特权容器,因为它提供了额外的安全层。

我建议使用 Ubuntu 14.04 作为 LXC 主机。我认为经过调整的 busybox 模板和适当的编译工具 运行 代码是您可以获得的最轻的容器。

这是我的想法:

// clone the prepared template
lxc-clone -o myTemplate -n newContainer

// put the code archive in the new container
cp path/to/code path/to/container/and/where/you/want

// Start the container as a daemon
lxc-start -n newContainer -d

// Then launch the right script for the type of code in the container
lxc-attach -n newContainer -- su containeruser -c /path/to/script.sh

所以这个小工作就是用所需的库创建模板。另一个工作是编写最后调用的脚本。

祝您项目顺利,希望对您有所帮助。

您想创建一个服务来编译 java 程序并启动它们。

我不明白你的问题不是关于如何安全地启动那些程序,因为你假设它已经被处理好了。

所以您想知道如何安全地启动 shell 脚本,您将在其中提供源代码的名称以及 javac.

的参数

首先,你有很多事情要做。

您想要使用系统调用这一事实意味着您将在整个虚拟主机中允许 exec。因此,如果您的 FTP 密码、您的 PHP 文件之一或任何东西遭到破坏,攻击者就可以上传脚本、二进制程序并执行它。

  1. 您的网站分区和临时文件夹必须在 fstab
  2. 中以 noexec 模式被 PHP 使用
  3. 您必须使用 open_basedir 限制来限制您在 PHP 内可以访问的文件,该限制从 safe_mode 末尾一直存在.然后您将允许 open_basedir 目录(当然没有 noexec 标志)
  4. 包含您的脚本和脚本的目录将具有 root.www-data 权限,并且不能被 www-data
  5. 写入
  6. 您将必须保护传递给脚本的参数(您已使用 escapeshellarg 完成)和 [=48 的文件名 中的任何潜在注入=] 文件(但我们假设它会在传输之前重命名,以避免在您将收到的不同 java 文件中发生文件名冲突)

PHP 中的安全模式并不像您想象的那么安全。

safe_mode_exec_dir 等函数限制了您可以从中 运行 一个应用程序或脚本的目录(或多个目录),但该应用程序或脚本可以调用该路径(或多个路径)之外的另一个目录。

大家都推荐你使用LXC,我都同意。这是您问题的最佳解决方案,但我会投入两分钱。

您可以为应用程序的每个用户准备一个 LXC 容器:

为每个用户创建一个本地用户(分离权限以避免其他用户访问或修改文件)并使用 SSH 登录到您的 LXC 容器以在其中执行您的任务:

此解决方案将允许您在登录 SSH 帐户后立即执行任务,而无需等待 creating/starting 每次需要 LXC 容器来执行您的任务时只需花费一点维护费用 运行宁.

您可以使用 SCP 发送和接收文件 to/from LXC:

并且您可以 "keep clean" 从后台进程发送(使用 root 用户)带有参数“-u”的 killall (-9),等等,或者使用 "pkill -u ..." 或彻底重启容器。

您甚至可以在每次要 运行 任务之前(以及在将必要的工作文件上传到 LXC 容器之前)重新创建用户(删除其主目录)。

此致。