本文为个人学习研究笔记,内容仅用于 SSRF 漏洞原理、复现环境搭建及安全防御技术研讨,所有实验均在自建、拥有完全授权的测试环境中完成。根据《网络安全法》,未经授权测试、攻击他人系统属于违法犯罪行为。禁止复制 本文Payload 对外部站点进行测试,违规使用造成的一切后果由使用者自行负责。
0. 先看一个"奇怪"的现象
假设你访问一个正常的网页快照服务,它长这样:
你输入一个网址 ──→ 服务器帮你抓取这个网址的内容 ──→ 返回给你
现在你在输入框里填了一个不是网址的东西,而是服务器自己内部的地址:
http://127.0.0.1:18080/admin
结果服务器居然老老实实地把"内部管理后台"的页面内容抓回来、返回给你了:
【服务端抓取结果】
<h1>内部管理后台</h1>
<p>欢迎回来,管理员。</p>
<p>当前登录身份:<b>root</b></p>
<p>内部 API 密钥:<code>flag{SSRF_内网探测_成功_0x7f000001}</code></p>
这里就出现了一个值得你停下来想 30 秒的问题:
这个"内部管理后台",本来应该只有服务器自己(或者内网里的人)才能访问,凭什么你一个外部的普通用户,随便提交一个 URL 就能看到它的内容?
答案就是本篇文章要讲的主角:SSRF(Server-Side Request Forgery,服务端请求伪造)。
1. SSRF 到底是什么意思
1.1 一个类比
把"服务器"想象成银行柜台的柜员 ,把"你"想象成站在柜台外的客户。
-
柜台的营业大厅是"外网",谁都能进;
-
柜台后面的金库是"内网",只有柜员(服务器自己)能进去。
正常业务里,你会说:"柜员,帮我把这张支票兑现。"柜员自己去金库取钱,然后把钱给你。
SSRF 就是:你对柜员说:"柜员,帮我跑一趟金库,把里面那个保险箱里的东西念给我听。"
柜员没有多想,真的进去把保险箱打开,把内容念给你听了。
关键点在于:不是你直接闯进了金库,而是你借了柜员(服务器)的身份和权限,让柜员替你进金库办事。 这中间没有任何"撬锁",因为柜员本来就有金库钥匙。
1.2 正式一点的定义
SSRF 是指:攻击者让服务器对攻击者指定的目标发起请求,而这个目标本来是攻击者无法直接访问的(通常是内网地址、或服务器本机)。
一句话版本:服务器被你当成了"代理",帮你访问它才能访问的地方。
1.3 为什么服务器会这么"听话"?
因为很多正常的业务功能,天然就需要"服务器替用户去访问某个 URL",比如:
| 业务功能 | 服务器需要做的事 | 真实案例场景 |
|---|---|---|
| 网页快照 | 抓取用户给的 URL 内容 | 搜索引擎快照、链接预览 |
| 图片代理 | 下载远程图片再展示 | 很多网站为了避免图片防盗链、做缩略图 |
| 文件导入 | 从 URL 导入文件(如"通过链接导入 Markdown") | 在线文档、笔记类产品 |
| 远程监控/体检 | 服务器去 ping/检查某个地址 | 站长工具、拨测系统 |
| 回调通知 | 服务器主动请求用户填的回调地址 | 支付回调、Webhook |
这些功能有一个共同点:服务器必须信任用户提供的 URL,并去请求它。 一旦开发者没有限制"这个 URL 可以指向哪里",SSRF 就诞生了。
你可能会想:这不就是把用户输入当 URL 去请求吗,为什么这么常见?因为大多数开发者只想到"用户会给我一个正常的网址",没想到"用户会给我一个内网地址或本机地址"。 这属于典型的"信任了不该信任的输入"。
2. 为什么 SSRF 危险?
很多初学者觉得:"不就是让服务器访问一下内网吗,能怎样?"
答案是:能怎样,取决于内网里有什么。 下面按危害从低到高排:
2.1 探测内网存活与端口(信息收集)
攻击者可以让服务器去访问 http://10.0.0.1、http://10.0.0.2......根据返回结果的时间差异、报错内容,判断内网有哪些主机、开了哪些端口、跑着什么服务。这是后续攻击的"地图"。
2.2 读取本机或内网的敏感信息
-
file:///etc/passwd读取服务器本地文件(如果协议没被禁用) -
访问内网的监控、后台、数据库管理界面(如 Redis、MySQL 管理台、Docker API 等)
2.3 打内网中"不设防"的服务(高危)
很多内网服务默认只监听 127.0.0.1,且没有密码,因为它们以为"只有本机程序能连我"。典型代表:
-
Redis(默认无密码,可通过 SSRF 的 gopher 协议直接下发命令 → 写 webshell → 拿下服务器)
-
Docker API (
/var/run/docker.sock) -
云元数据服务 (AWS/阿里云/腾讯云的
169.254.169.254,能拿到服务器的临时凭据)
这里有一个反直觉的点,值得记住:内网服务"不设防"恰恰是因为它认为攻击者够不着它。 而 SSRF 的价值,就是把"够不着"变成"够得着"。
2.4 攻击云厂商元数据接口(真实高频漏洞)
在公有云上,每台虚拟机都能通过一个魔法 IP 169.254.169.254 访问到"元数据服务",里面有这台机器的 IAM 临时密钥、SSH 公钥、内网 IP 等。如果云上某个应用存在 SSRF,攻击者常常直接:
http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>
拿到临时密钥,进而接管云资源。这是国内外各大 SRC(漏洞众测平台)里 SSRF 最常见、最值钱的利用方式。
3. 靶场
它由三部分组成:
| 角色 | 说明 | 监听地址 |
|---|---|---|
| 漏洞靶机 | 一个"网页快照/图片代理"服务,存在 SSRF | 0.0.0.0:8080(对外) |
| 内网管理后台 | 持有敏感 flag 的内部服务,只监听 127.0.0.1 | 127.0.0.1:18080 |
| 内网 Redis | 无密码的内网数据库,只监听 127.0.0.1 | 127.0.0.1:6379 |
关键设定:内网服务和 Redis 都只绑定在 127.0.0.1 上,外部用户直接访问是访问不到的(在真实环境里,它们处于防火墙后的内网)。我们就是要通过漏洞靶机这个"柜员",去够到它们。
3.1 漏洞靶机代码(vuln.php)
<?php
// 场景1:无任何过滤的抓取(最基础的 SSRF)
if (isset($_GET['fetch'])) {
$url = $_GET['fetch'];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 跟随跳转(伏笔:后面绕过用)
$res = curl_exec($ch);
curl_close($ch);
echo "【服务端抓取结果】\n" . $res;
exit;
}
?>
这段代码非常"典型":用户输入的 $url 直接被塞进了 curl_setopt(..., CURLOPT_URL, $url),没有任何校验。 这就是 SSRF 的根源。
3.2 内网管理后台代码(internal_admin.py)
import http.server, socketserver
FLAG = "flag{SSRF_内网探测_成功_0x7f000001}"
class AdminHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/admin':
body = f"<h1>内部管理后台</h1>...内部 API 密钥:{FLAG}...".encode('utf-8')
self.send_response(200)
# ... 返回 HTML
# ...
# 关键:只绑定 127.0.0.1,模拟"仅内网可访问"
socketserver.TCPServer(("127.0.0.1", 18080), AdminHandler).serve_forever()
注意最后一行:服务只绑定在 127.0.0.1。这意味着在真实网络里,外部用户连 18080 端口都连不上。
3.3 启动靶场
# 1. 启动内网管理后台(只监听 127.0.0.1:18080)
python3 internal_admin.py &
# 2. 启动无密码 Redis(只监听 127.0.0.1:6379)
redis-server --port 6379 --bind 127.0.0.1 --protected-mode no &
# 3. 启动漏洞靶机(监听所有网卡 0.0.0.0:8080)
php -S 0.0.0.0:8080 vuln.php
验证一下:直接
curl http://127.0.0.1:18080/admin能访问,但从"外部网络"是访问不到的------这就是 SSRF 要跨越的那道墙。
4. 第一次利用:最基础的 SSRF
4.1 正常用法 vs 攻击用法
靶机首页提供了一个表单,输入 URL。正常用户会输入 http://example.com。而攻击者输入的是内网地址:
curl "http://127.0.0.1:8080/vuln.php?fetch=http://127.0.0.1:18080/admin"
返回:
【服务端抓取结果】
<h1>内部管理后台</h1>
<p>欢迎回来,管理员。</p>
<p>当前登录身份:<b>root</b></p>
<p>内部 API 密钥:<code>flag{SSRF_内网探测_成功_0x7f000001}</code></p>
拿到 flag 了。
4.2 这背后发生了什么?(请求链路)
你 (外部用户)
│ 发送:fetch=http://127.0.0.1:18080/admin
▼
漏洞靶机 (192.168.x.x:8080) ← 你的请求只到了这一层
│ 靶机自己去请求:http://127.0.0.1:18080/admin
▼
内网管理后台 (127.0.0.1:18080) ← 这一层,你本来够不到
│ 返回:内部 API 密钥 flag{...}
▼
漏洞靶机 → 把结果原样返回给你
关键理解 :请求是靶机发出的,不是你的浏览器发出的。所以"谁能访问 127.0.0.1:18080"这个问题上,用的是靶机的身份------靶机当然能访问它自己的 127.0.0.1。
你可能的疑问:127.0.0.1 不是我自己电脑吗?为什么写 127.0.0.1 是访问服务器? 这里要分清"这段 URL 是谁去解析的"。
127.0.0.1这个地址,在靶机的视角 里,指的就是靶机自己。因为最终是靶机去curl这个 URL,所以127.0.0.1就是"靶机本机"。你的浏览器从没直接请求过 127.0.0.1,它只是把字符串127.0.0.1传给了靶机。
5. SSRF 常用协议(由易到难逐个看)
SSRF 的威力很大程度上来自"服务器不止能发 HTTP 请求,还能用别的协议"。我们先认识三个,够用。
5.1 http:// 和 https://(最常用)
就是普通的网页请求,能访问内网任意 HTTP 服务。上面第 4 节用的就是它。
5.2 file://(读本地文件,最直观)
如果开发者的过滤只拦了内网 IP,没拦协议,攻击者可以:
curl "http://127.0.0.1:8080/vuln.php?fetch=file:///etc/passwd"
真实返回:
【服务端抓取结果】
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
file:///etc/passwd 让服务器把自己本地文件读出来返回给你。危害不言而喻。
你可能的疑问:为什么是三个斜杠
file:///?file://后面跟的是文件路径。Unix 的绝对路径本身就是以/开头,所以file://+/etc/passwd=file:///etc/passwd。中间的//是协议分隔符,最后那个/是路径开头的斜杠。
5.3 gopher://(最厉害,能"投喂"任意 TCP 数据)
这是 SSRF 进阶的核心,也是下篇的重点。这里先给你一个直觉:
-
http://只能发"符合 HTTP 格式"的请求; -
gopher://能让你直接往目标端口塞任意字节 (_后面跟的字节会被原样发过去)。
这意味着:只要内网服务是"发文本/字节就能控制"的协议(比如 Redis、MySQL 的文本协议),gopher 就能直接跟它"对话"。这是 SSRF 从"读数据"升级到"执行命令/写文件"的关键一步。
6. 小结(上篇)
到这里你应该已经掌握:
-
SSRF 是什么:让服务器替你访问它才能访问的地方(内网/本机/云元数据)。
-
为什么会有:业务需要服务器请求用户提供的 URL,而开发者没限制目标。
-
危害:探测内网 → 读敏感信息 → 打无密码内网服务 → 拿云凭据。
-
第一次利用 :
fetch=http://127.0.0.1:18080/admin拿到了内网后台的 flag。 -
三种协议 :
http(访问网页)、file(读文件)、gopher(投喂任意 TCP 数据)。
下篇预告:真实的漏洞往往不会这么"裸",开发者会加各种过滤(黑名单、白名单、限制协议)。下篇我们讲:
-
如何绕过
127.0.0.1黑名单(十六进制、十进制、八进制、302 跳转......) -
如何用
gopher://真实打穿无密码 Redis,写入 webshell 实现 RCE -
完整的攻击链演示 + 防御建议