五.SSRF 漏洞EXP技巧,典例分析以及 如何修复 (下册)
目录
五.SSRF 漏洞EXP技巧,典例分析以及 如何修复 (下册)
5.4gopher 协议初探
0x01 Gopher协议
0x02 协议访问学习
复现环境
centos7 + kali 2018
发送http get请求
发送http post请求
5.5 SSRF的防御与绕过
0x01 常见的过滤
0x02 绕过
点分割符号替换
本地回环地址
IP的进制转换
封闭式字母数字 (Enclosed Alphanumerics)字符
URL十六进制编码
利用网址缩短
利用30X重定向
补充vps:
DNS解析
xip.io
SSRF的测试工具
SSRFmap
SSRF-Testing
redis-over-gopher
SSRF的加固
5.6ssrf攻击fastcgi复现及环境搭建
环境搭建
开始攻击
5.8 SSRF漏洞之FastCGI利用篇
0x00.PHP-FPM FastCGI 未授权利用
0x01.CGI、FastCGI、PHP-FPM
PHP-FPM通信方式
0x02.FastCGI攻击原理
FastCGI协议
漏洞原理
0x03.SSRF攻击本地的PHP-FPM
5.4gopher 协议初探
- 最近两天看到了字节脉搏实验室公众号上有一篇《Gopher协议与redis未授权访问》的文章,其中对gopher协议进行了比较详细的介绍,所以打算跟着后面复现学习一下。
0x01 Gopher协议
gopher
协议是一种信息查找系统,他将Internet
上的文件组织成某种索引,方便用户从Internet
的一处带到另一处。在WWW
出现之前,Gopher
是Internet
上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70
端口。但在WWW
出现后,Gopher
失去了昔日的辉煌。现在它基本过时,人们很少再使用它。它只支持文本,不支持图像
0x02 协议访问学习
我们现在最多看到使用这个协议的时候都是在去
ssrf
打redis shell
、读mysql
数据的时候,由于之前对这个协议了解不是很熟,所以这次看到这篇文章后打算借此学习一下他的通信方式首先最基础的看一下它如何发送
get
请求复现环境
centos7 + kali 2018
在
centos7
主机使用nc
监听端口,nc -lvp 6666
然后用
kali
使用curl gopher://ip:6666/_abcd
发送gopher get
请求,可以发现_
不会被显示
gopher
协议格式:gopher://IP:port/_{TCP/IP数据流}
发送http get请求
在gopher协议中发送HTTP的数据,需要以下三步
构造
HTTP
数据包
URL
编码、替换回车换行为%0d%0a
,HTTP
包最后加%0d%0a`代表消息结束发送
gopher
协议, 协议后的IP
一定要接端口
curl gopher://192.168.109.166:80/_GET%20/get.php%3fparam=Konmu%20HTTP/1.1%0d%0aHost:192.168.109.166%0d%0a
get.php
中写入<?php echo "Hello"." ".$_GET['param']."\n"?>
此外自己本地测试时要注意将防火墙关掉
发送http post请求
POST
与GET
传参的区别:它有4
个参数为必要参数需要传递
Content-Type
,Content-Length
,host
,post
的参数
post.php
中写入<?php echo "Hello".$_POST['name']."\n";?>
POST与GET传参的区别:它有4个参数为必要参数POST /post.php HTTP/1.1host:192.168.194.1Content-Type:application/x-www-form-urlencodedContent-Length:12name=purplet如下构造:curl gopher://192.168.194.1:80/_POST%20/post.php%20HTTP/1.1%0d%0AHost:192.168.194.1%0d%0AContent-Type:application/x-www-form-urlencoded%0d%0AContent-Length:12%0d%0A%0d%0Aname=purplet%0d%0A
5.5 SSRF的防御与绕过
- SSRF漏洞形成的原因主要是服务器端所提供的接口中包含了所要请求的内容的URL参数,并且未对客户端所传输过来的URL参数进行过滤
- 一般的防御措施是对URL参数进行过滤,或者使得URL参数用户不可控,但当过滤方法不当时,就存在Bypass的不同方式
0x01 常见的过滤
- 过滤开头不是http://xxx.com的所有链接
- 过滤格式为ip的链接,比如127.0.0.1
- 结尾必须是某个后缀
0x02 绕过
http://www.baidu.com@10.10.10.10
与http://10.10.10.10
请求是相同的- 该请求得到的内容都是10.10.10.10的内容,此绕过同样在URL跳转绕过中适用。
- 原理如下:利用解析URL时的规则问题。
- 一般情况下利用URL解析导致SSRF过滤被绕过基本上都是因为后端通过不正确的正则表达式对URL进行了解析。而在2017年的Blackhat大会上,Orange Thai 在blackhat中发表的演讲《A New Era of SSRF - Exploiting URL Parser in Trending Programming Languages! 》中介绍了SSRF攻击的一个新的角度———利用不同编程语言对URL的处理标准来绕过SSRF过滤,从而实施攻击。该方式主要是利用URL解析器和URL请求器之间的差异性发起攻击,由于不同的编程语言实现URL解析和请求是不一样的,所以很难验证一个URL是否合法。
- 很难验证一个URL是否合法的原因在于:
- 1.在 RFC2396/RFC3986 中进行了说明,但是也仅仅是说明。2.WHATWG(网页超文本应用技术工作小组)定义了一个基于RFC协议的具体实现,但是不同的编程语言仍然使用他们自己的实现。
- 下图展示了cURL请求函数与其他语言解析函数结合使用时,由于差异性造成的漏洞。(本图加载时出现了问题)
- 可以得知,NodeJS url、Perl URI、Go net/url、PHP parser_url以及Ruby addressable解析函数与cURL libcurl请求函数差异性都可能造成漏洞的产生
- 下图的实例中,我们看到上述所述编程语言的解析函数得到的IP是google.com,而cURL请求得到的却是evil.com:80
点分割符号替换
- 在浏览器中可以使用不同的分割符号来代替域名中的
.
分割,可以使用。
、。
、.
来代替:http://www。qq。com http://www。qq。com http://www.qq.com 无效的绕过方式
本地回环地址
- 127.0.0.1,通常被称为本地回环地址(Loopback Address),指本机的虚拟接口,一些表示方法如下(ipv6的地址使用http访问需要加
[]
):http://127.0.0.1 http://localhost http://127.255.255.254 127.0.0.1 - 127.255.255.254 http://[::1] http://[::ffff:7f00:1] http://[::ffff:127.0.0.1] http://127.1 http://127.0.1 http://0:80
IP的进制转换
- IP地址是一个32位的二进制数,通常被分割为4个8位二进制数。通常用“点分十进制”表示成(a.b.c.d)的形式,所以IP地址的每一段可以用其他进制来转换。 IPFuscator 工具可实现IP地址的进制转换,包括了八进制、十进制、十六进制、混合进制。在这个工具的基础上添加了IPV6的转换和版本输出的优化。
- 在脚本对IP进行八进制转换时,一些情况下会在字符串末尾多加一个L。
封闭式字母数字 (Enclosed Alphanumerics)字符
- 封闭式字母数字是一个由字母数字组成的Unicode印刷符号块,使用这些符号块替换域名中的字母也可以被浏览器接受。在浏览器测试中只有下列单圆圈的字符可用:
List: ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ ⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇ ⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ ⒒ ⒓ ⒔ ⒕ ⒖ ⒗ ⒘ ⒙ ⒚ ⒛ ⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ ⒱ ⒲ ⒳ ⒴ ⒵ Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ⓠ ⓡ ⓢ ⓣ ⓤ ⓥ ⓦ ⓧ ⓨ ⓩ ⓪ ⓫ ⓬ ⓭ ⓮ ⓯ ⓰ ⓱ ⓲ ⓳ ⓴ ⓵ ⓶ ⓷ ⓸ ⓹ ⓺ ⓻ ⓼ ⓽ ⓾ ⓿ ⓧⓘⓐⓝⓞⓤⓟⓔⓝⓖ.ⓒⓞⓜ ①⑦②.①⑥.⑥⓪.①⑥⑥ 经测试,不可行,也许有其他方式
- 浏览器访问时会自动识别成拉丁英文字符:
ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ >>> example.com
URL十六进制编码
- URL十六进制编码可被浏览器正常识别,编码脚本:
#-*- coding:utf-8 -*- data = "www.qq.com"; alist = [] for x in data: for i in range(0, len(x), 2): alist.append((x[i:i+2]).encode('hex')) print "http://%"+'%'.join(alist)
利用网址缩短
- 网上有很多将网址转换未短网址的网站。
利用30X重定向
- 可以使用重定向来让服务器访问目标地址,可用于重定向的HTTP状态码:300、301、302、303、305、307、308。
- 需要一个vps,把302转换的代码部署到vps上,然后去访问,就可跳转到内网中
补充vps:
VPS是Virtual Private Server的缩写,即虚拟私人服务器。它是通过虚拟化技术在物理服务器上划分出的一部分资源,每个VPS都像一个独立的服务器,具有自己的操作系统、CPU、内存、存储空间和网络连接。**VPS的一些关键特点和用途包括**:1. **资源隔离**:每个VPS都有独立的资源,不会受到其他VPS影响。2. **灵活性**:可以根据需求选择不同的操作系统、配置以及应用程序部署。3. **成本效益**:相比于独立服务器,VPS通常价格更为经济,适合中小型网站和应用的运行。4. **易于管理**:提供了方便的控制面板和远程访问方式,管理者可以通过这些接口轻松管理VPS的配置和运行状态。5. **安全性**:VPS之间是隔离的,因此安全性较高,一般不会因为其他VPS的问题而受到影响。对于你的需求,部署302重定向代码到VPS上可以通过以下步骤完成:1. **选择VPS提供商**:选择一个信誉良好的VPS提供商,比如DigitalOcean、Linode、AWS等。注册账号并购买适合你需求的VPS实例。2. **配置VPS**:在VPS提供商的管理控制面板中,选择你购买的VPS实例,并进行初始化配置。这通常包括选择操作系统、设置主机名、SSH密钥等。3. **连接到VPS**:使用SSH客户端连接到你的VPS,可以在命令行中操作VPS。例如,使用`ssh username@vps_ip_address`命令连接。4. **部署代码**:将你的302重定向代码上传到VPS中,可以通过FTP、SCP或者Git等方式上传到VPS的合适目录中,通常是Web服务器的根目录或者虚拟主机目录下。5. **配置Web服务器**:如果你使用的是Apache、Nginx等Web服务器,确保配置正确的重定向规则,以便访问VPS时能够正确执行302重定向。6. **测试和调试**:完成部署后,通过浏览器或者curl命令等方式测试你的302重定向是否正确工作。确保访问VPS时能够成功跳转到内网中指定的地址。7. **监控和维护**:定期监控VPS的运行状态和安全性,及时更新操作系统和相关软件,以确保VPS的稳定和安全运行。
- 服务端代码如下:
<?php header("Location: http://192.168.1.10"); exit(); ?>
DNS解析
- 配置域名的DNS解析到目标地址(A、cname等),这里有几个配置解析到任意的地址的域名:
nslookup 127.0.0.1.nip.ionslookup owasp.org.127.0.0.1.nip.io 在内网外网可以解析
xip.io
- xip.io是一个开源泛域名服务。它会把如下的域名解析到特定的地址,其实和dns解析绕过一个道理。
http://10.0.0.1.xip.io = 10.0.0.1 www.10.0.0.1.xip.io= 10.0.0.1 http://mysite.10.0.0.1.xip.io = 10.0.0.1 foo.http://bar.10.0.0.1.xip.io = 10.0.0.1 10.0.0.1.xip.name resolves to 10.0.0.1 www.10.0.0.2.xip.name resolves to 10.0.0.2 foo.10.0.0.3.xip.name resolves to 10.0.0.3 bar.baz.10.0.0.4.xip.name resolves to 10.0.0.4 不可行 在内网外网不能解析
SSRF的测试工具
SSRFmap
- SSRFmap-master - 可以在一个请求包中指定SSRF的位置,工具根据模块来发送EXP,支持了下列漏洞的利用:
- 帮助说明如下:
SSRF-Testing
- SSRF-Testing-master - 常用的SSRF绕过测试
- https://secpulseoss.oss-cn-shanghai.aliyuncs.com/wp-content/uploads/1970/01/beepress-image-172472-1641438206.png
redis-over-gopher
- redis-over-gopher - 将请求转换为gopher协议格式
- https://secpulseoss.oss-cn-shanghai.aliyuncs.com/wp-content/uploads/1970/01/beepress-image-172472-1641438208.png
SSRF的加固
- •禁止302跳转,或者每跳转一次都进行校验目的地址是否为内网地址或合法地址。
- •过滤返回信息,验证远程服务器对请求的返回结果,是否合法。
- •禁用高危协议,例如:gopher、dict、ftp、file等,只允许http/https
- •设置URL白名单或者限制内网IP
- •限制请求的端口为http的常用端口,或者根据业务需要治开放远程调用服务的端口
- •catch错误信息,做统一错误信息,避免黑客通过错误信息判断端口对应的服务
5.6 ssrf攻击fastcgi复现及环境搭建
环境搭建
- 7.2版本的成功复现
docker run -it --name t1 -p 127.0.0.1:12313:80 ubuntu 先docker 起一个干净的ubuntu
- 这里可以看到端口已经成功映射过来了
- 简单解释几下,先增加一些其他的源,然后down下来php7.2以及他的一些扩展,还有nginx 修改nginx,和php-fpm的配置文件
主要配置文件修改的就是这几个地方fpm的www.conf文件 listen = 127.0.0.1:9000nginx的default文件 fastcgi_pass 127.0.0.1:9000;
- 然后在/var/www/html目录下创建
/*phpinfo.php 1.php*/ <?php highlight_file(__FILE__); // 在页面上高亮显示当前文件的源代码,便于调试和分析$url = $_GET['url']; // 从URL参数中获取名为'url'的值,这个值应该是用户提供的URL$curl = curl_init($url); // 初始化一个curl会话,准备从用户提供的URL获取内容curl_setopt($curl, CURLOPT_HEADER, 0); // 设置curl选项,禁止将HTTP头包含在输出中$responseText = curl_exec($curl); // 执行curl会话并获取内容,将内容保存在$responseText中echo $responseText; // 输出从用户提供的URL获取的内容curl_close($curl); // 关闭curl会话,释放资源 ?>
开始攻击
- 很明显的ssrf漏洞了
- http://127.0.0.1:12313/1.php?url=http://www.baidu.com
- 可以成功返回百度页面
- 利用gopher协议去伪造请求
- 用Gopherus-master工具生成exp
- 可以看到成功执行命令了
5.7 SSRF漏洞之FastCGI利用篇
- SSRF–(Server-side Request Forge, 服务端请求伪造)
- 定义:由攻击者构造的攻击链接传给服务端执行造成的漏洞,一般用来在外网探测或攻击内网服务
- SSRF漏洞思维导图如下,本篇主要介绍利用SSRF漏洞攻击FastCGI
0x00.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权限
- 看到这里,相比同学们都很好奇为何只是开启9000端口就造成任意命令执行了呢?
- 啥是PHP-FPM,FastCGI又是啥(大佬请略过0x01章节~)
- 接下来,我们一起探究漏洞的原理和具体的利用过程吧~
0x01.CGI、FastCGI、PHP-FPM
- 我们知道,在网站架构中,Web Server(如Nginx)只是内容的分发者
- 当客户端请求的是index.php,根据配置文件Web Server辨别不是静态文件,此时就需要去找 PHP解析器来处理
- 当Web Server收到 index.php 这个请求后,会启动对应的CGI 程序,也就是PHP解析器
- 接下来PHP解析器会解析php.ini文件,初始化执行环境,然后处理请求,再以CGI规范的格式返回处理后的结果,退出进程,Web server再把结果返回给浏览器。这就是一个完整的动态PHP Web访问流程 这其中,引出如下概念:
CGI:是 Web Server 与 Web Application 之间数据交换的一种协议 **FastCGI:**同 CGI,是一种通信协议,对比 CGI 提升了5倍以上性能 **PHP-CGI:**是 PHP(Web Application)对 Web Server 提供的 CGI 协议的接口程序 **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服务器的请求。
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模式下进行
0x02.FastCGI攻击原理
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; // FastCGI 协议版本号unsigned char type; // 记录类型(如请求、响应等)unsigned char requestIdB1; // 请求ID的高字节unsigned char requestIdB0; // 请求ID的低字节unsigned char contentLengthB1; // 内容体长度的高字节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', # 网关接口的版本,这里是FastCGI协议的版本号'REQUEST_METHOD': 'GET', # HTTP请求方法,这里是GET请求'SCRIPT_FILENAME': '/var/www/html/index.php', # 被执行的脚本的文件名'SCRIPT_NAME': '/index.php', # 脚本名称,相对于DOCUMENT_ROOT的路径'QUERY_STRING': '?a=1&b=2', # 请求的查询字符串'REQUEST_URI': '/index.php?a=1&b=2', # 包含查询字符串的完整请求URI'DOCUMENT_ROOT': '/var/www/html', # 当前运行脚本的文档根目录'SERVER_SOFTWARE': 'php/fcgiclient', # 服务器软件名称及版本'REMOTE_ADDR': '127.0.0.1', # 客户端的IP地址'REMOTE_PORT': '12345', # 客户端的端口号'SERVER_ADDR': '127.0.0.1', # 服务器的IP地址'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
漏洞原理
- 到这里,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', # FastCGI协议版本'REQUEST_METHOD': 'GET', # 请求方法,这里是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', # 包含了请求的URI的字符串'DOCUMENT_ROOT': '/var/www/html', # 当前运行脚本所在的文档根目录'SERVER_SOFTWARE': 'php/fcgiclient', # 服务器标识字符串'REMOTE_ADDR': '127.0.0.1', # 客户端的IP地址'REMOTE_PORT': '12345', # 客户端的端口号'SERVER_ADDR': '127.0.0.1', # 服务器的IP地址'SERVER_PORT': '80', # 服务器的端口号'SERVER_NAME': "localhost", # 服务器的主机名'SERVER_PROTOCOL': 'HTTP/1.1', # 请求使用的协议版本'PHP_VALUE': 'auto_prepend_file = php://input', # PHP配置选项,用于指定自动预加载的文件为php://input'PHP_ADMIN_VALUE': 'allow_url_include = On' # PHP管理员配置选项,允许包含远程文件 }
- 最后两行设置
auto_prepend_file = php://input
且allow_url_include = On
,然后将我们需要执行的代码放在Body中,即可执行任意代码0x03.SSRF攻击本地的PHP-FPM
- 生产环境中,除非测试或者图方便之外,PHP-FPM是极少开放在公网的,绝大部分都是启动在本地即监听127.0.0.1:9000地址,这种情况下,如果服务器端存在SSRF漏洞,那么我们就可以借助SSRF来攻击本地PHP-FPM服务,达到任意代码执行的效果