本文分析了一种利用php-fpm文件描述符泄露实现的php内存马。
php无文件攻击(三) - php-fpm文件描述符泄露
本文思路来源于imbeee师傅的文章(膜膜膜) https://www.anquanke.com/post/id/163197
一、写在前面
Linux进程使用文件描述符(FD)来管理打开的文件。
php-fpm运行的php脚本里,使用system()等函数执行外部程序时,由于php-fpm没有使用FD_CLOEXEC处理FD,导致fork出来的子进程会继承php-fpm进程的所有FD。
举个栗子:
这是当前php-fpm work的所有FD:
<?php system("sleep 60");>
php-fpm在执行该文件时,会fork sleep 子进程。 sleep子进程会继承了父进程php-fpm的FD,其中包括一个关键FD:php-fpm监听的9000端口的socket ,这里是5号FD。(原文中一直在声明是0号FD,可能该值并不固定)
sleep进程号为1771,可见sleep进程继承了5号FD:
在子进程里有了继承来的socket FD,就可以直接使用accept函数直接从该socket接受一个连接。
测试下:
index.php
<?php
// t2.php
system("/tmp/test");
/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);
//直接使用5 fd作为socket句柄,原文中是fd号为0
sockfd = 5;
//这里accept会阻塞,接受连接后才会执行system()
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
system("/bin/touch /tmp/lol");
return 0;
}
编译后,访问index.php。发现被阻塞了。此时访问fpm的任意文件,test进程接收到socket连接,执行system()。
还有一点,test进程在/proc/下面的文件所属用户是www(php-fpm的运行用户)而不是root(php-fpm的master进程所属用户为root),也就是说子进程继承的worker的运行权限。
二、利用方式
我们的终极目标是,在php中建立一个socket,通过该socket 操作 php-fpm的socket。
测试一下,在php中建立一个socket:
<?php
// t3.php
sleep(10);
$socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
sleep(10);
原本的worker只有 0 1 2 5四个FD。
php脚本新建socket后,多了一个3号FD(其实测试socket阻断那里就已经发现是3号FD了),也就是说我们通过一个子进程将5号FD复制到3号FD,即可实现通过该socket接管php-fpm的socket。
完成代码可见原文,这里不再赘述。下面分析下具体思路:
- php脚本运行后先删除自身
- php脚本创建一个socket,并获取FD号。
- php脚本调用system()建立一个子进程,该子进程会继承worker运行权限。
- 子进程attach到父进程(php-fpm worker),向父进程中注入复制FD的shellcode(shellcode作用为调用dup2命令,将php-fpm socket的FD号,复制到php创建的socket的FD号(在我的测试中就是5号复制到3号)。
- 子进程恢复worker进程状态后detach,退出。
整个过程完成后,php代码中的socket即可操作php-fpm的socket。
如想完成webshell功能,可以解析请求fast-cgi请求,如果包含指令,拦截并执行。
否则正常转发到9000端口让正常的worker处理即可。
三、总结
该方法虽然实现了对php-fpm的无文件攻击,但是个人觉得局限性较高,利用场景可能比较局限:
- 环境的限制:只能在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 work本身的限制:生产环境中php-fpm的worker进程众多,fast-cgi请求能被污染后的worker accept 接受到的概率很低。
- php-fpm socket FD号并不固定,个人觉得加个遍历比较好。
除此之外,子进程向worker进程注入shellcode的操作应该有更优雅的姿势,希望师傅们可以关注下。
相关代码完善后会同步至github。
- 本文作者: jweny
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/105
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!