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 域中,后续访问也就继续受该域约束。
备注:内容仅供参考。
