PAYATHON 2026

SELinux 下的 php-fpm 为何能在 httpd_t 域中执行 /bin/sh?

排障赵师傅

结论

如果 SELinux 处于 Enforcing 模式,且 httpd_t 没有被单独设为 permissive,这种现象说明当前加载的策略允许 httpd_t 执行这些程序,并在执行后保持 httpd_t 域。这里涉及的权限是 execute_no_trans。

bin_t 是文件类型,不能据此判断 Web 服务是否有权执行该文件。是否允许执行,取决于策略对源域、目标文件类型、对象类别和权限的规定。同样,httpd_t 这个域名本身也不表示禁止启动 shell。

/bin/sh 通常是符号链接,最终指向的文件可能标记为 shell_exec_t。需要检查实验机上的实际标签,不能直接认定为 bin_t。

为什么执行 shell 后仍然是 httpd_t

进程域和文件类型是两回事:

  • httpd_t 描述进程所处的安全域。
  • bin_t、shell_exec_t 描述可执行文件的类型。

执行标记为 bin_t 的程序,不会让进程进入所谓的“bin_t 域”。SELinux 的域转换需要相应的策略规则,执行文件本身不一定触发转换。

这里观察到的执行路径可以简化为:

php-fpm:httpd_t
    |
    | 创建子进程,继承原来的域
    v
子进程:httpd_t
    |
    | 执行 /bin/sh,策略允许不转换域的执行
    v
sh:httpd_t
    |
    | 执行 sleep、touch 等外部程序
    v
程序进程:httpd_t

相关文件权限包括:

权限作用
execute允许执行目标文件
execute_no_trans允许执行目标文件并保持原来的域
entrypoint在发生域转换时,允许该文件作为目标域的入口程序

例如,下面的规则允许 httpd_t 执行 bin_t 文件且不转换域:

allow httpd_t bin_t:file { execute execute_no_trans };

这条规则仅用于说明机制,不是实验机上的实际查询结果,也不包含执行所需的全部授权。执行过程中还可能涉及 read、open、map 等权限。

实际策略也可能通过类型属性授予权限,因此查询结果中的规则未必直接写着 httpd_t 或 bin_t。

为什么 PHP 文件的内容标签没有阻止 system()

php-fpm 处理 PHP 脚本时,通常由已经运行的解释器读取脚本内容,再解释执行。内核没有直接将这个 PHP 文件作为可执行文件启动。

因此,即使 PHP 文件标记为 httpd_sys_content_t,其中的 PHP 代码仍可能调用 system()。执行到该函数时,php-fpm 进程以自身的 httpd_t 域发起相关操作,SELinux 按这个域检查后续操作。

这里要分别检查两类权限:

httpd_t 能否读取 PHP 文件?
httpd_t 能否执行 shell,以及访问命令涉及的资源?

内容文件的标签控制前一个问题,不会为 PHP 源码中的函数调用建立单独的权限边界。

题目中的 httpd_sys_content 和 httpd_sys_rw_content 也需要核对。Fedora 策略中通常使用的完整类型名是 httpd_sys_content_t 和 httpd_sys_rw_content_t,应以 ls -Z 输出为准。

如何查明实验机上生效的规则

1. 确认 SELinux 确实在强制执行

getenforce
sestatus
sudo semanage permissive -l

getenforce 应显示 Enforcing,同时还要检查 httpd_t 是否被单独配置为 permissive。即使全局处于 Enforcing,单独设为 permissive 的域也只记录拒绝,不阻止操作。

再检查进程标签:

ps -eo label,pid,ppid,comm,args

核对实际处理请求的 php-fpm worker 及其子进程的标签,仅查看服务名称还不够。

2. 检查 /bin/sh 的最终目标

ls -lZ /bin/sh
readlink -f /bin/sh
ls -lZ "$(readlink -f /bin/sh)"

ls -lZ /usr/bin/sleep /usr/bin/touch

第一条命令可能显示符号链接本身的标签。要确定执行目标的类型,还需查看链接最终指向的文件。

3. 用 sesearch 查询执行权限

如果目标类型确实是 bin_t,可以查询:

sesearch -A -s httpd_t -t bin_t -c file -p execute -C
sesearch -A -s httpd_t -t bin_t -c file -p execute_no_trans -C

如果 shell 的实际类型是 shell_exec_t,则查询:

sesearch -A -s httpd_t -t shell_exec_t -c file -p execute -C
sesearch -A -s httpd_t -t shell_exec_t -c file -p execute_no_trans -C

-C 用于显示条件表达式。如果匹配到的规则受布尔值控制,再检查相应开关:

getsebool -a

规则可能通过类型属性匹配,因此需要检查完整查询结果,不能只搜索以下字面形式:

allow httpd_t bin_t:file ...

要确定 Fedora 44 上究竟是哪条规则、哪个属性或哪个布尔值提供了授权,需要实验机的策略查询输出。仅凭发行版名称,无法确定当前加载的策略内容。

怎样验证 SELinux 实际限制了什么

sleep 能运行,只说明启动程序及其所需的操作获得了授权。touch 能启动,也不代表它能在任意目录创建文件。

创建文件还需要父目录的搜索、写入和 add_name 等权限,以及新文件的 create 权限。SELinux 会继续检查 shell 及其子进程对资源的访问。

可以通过现有实验入口,在两个已确认标签的目录中分别测试:

touch /var/www/html/selinux-test
touch /var/www/html/uploads/selinux-test

测试的前提是:

  • /var/www/html 标记为 httpd_sys_content_t。
  • /var/www/html/uploads 已存在,标记为 httpd_sys_rw_content_t。
  • Unix 文件权限允许 apache 用户在这两个目录中创建文件。

如果策略没有额外放宽授权,可以预期普通内容目录的写入受到限制,可写内容目录则允许应用写入。实际结果仍取决于加载的规则和布尔值,例如 httpd_unified 可能影响这种区分。

测试后查看审计记录:

sudo ausearch -m AVC,USER_AVC -ts recent -i

检查拒绝记录中的源上下文 scontext、目标上下文 tcontext、对象类别 tclass 和被拒绝的权限。如果命令失败却没有相应 AVC,还需检查 Unix 权限、路径是否存在等原因。

测试应通过实际的 php-fpm 请求路径进行。sudo -u apache 只切换 Unix 用户,不能保证命令运行在 httpd_t 域中。

如果目标就是禁止启动 shell

先查询现有的执行授权,再决定如何收紧策略。如果权限来自可关闭的条件规则,可以评估关闭相应布尔值;如果授权是无条件的,通常需要调整策略设计,或为应用建立权限更小的专用域。常规本地 allow 模块用于增加权限,无法通过再添加一个模块来抵消已有的允许规则。

某个 HTTP 布尔值即使名称中包含“脚本”或“执行”,也不能直接当作禁止 PHP system() 的总开关。它是否控制这条执行路径,需要通过策略查询确认。

即使禁止了 /bin/sh,PHP 代码仍可能直接使用文件或网络 API。要限制入侵后的影响,还应验证 httpd_t 对敏感文件、可写目录和网络目标的访问边界。shell 及其子进程保持在 httpd_t 域中,后续访问也就继续受该域约束。

备注:内容仅供参考。