SSRF(Server-Side Request Forgery,服务器端请求伪造)并不是一个只靠“禁止访问内网 IP”就能彻底解决的问题。实际审计中,漏洞往往出现在图片抓取、Webhook 回调、文档转换、远程资源导入、健康检查等功能里。开发者通常只关注了参数是否为合法 URL,却忽略了 URL 解析差异、DNS 解析变化、重定向链以及非 HTTP 协议等边界情况。
本文以代码审计和修复验证为主线,整理一套适用于 Web 应用的 SSRF 分析方法。示例均以本地测试服务或受控地址为前提,不涉及访问真实内网资产、云平台元数据或第三方系统。
一、典型业务场景与风险边界
常见的 SSRF 入口包括以下几类:
- 根据用户提交的 URL 下载头像、附件或远程图片。
- 服务端生成 PDF、截图或预览页面时访问指定地址。
- Webhook、回调地址、消息推送等功能由用户配置目标 URL。
- 导入 RSS、OpenAPI 文档或远程配置文件。
- 后台健康检查功能允许管理员输入待检测地址。
这类功能本身并不一定存在漏洞,关键在于服务端是否会按照用户可控的地址发起网络请求,以及请求是否能够到达不应暴露的网络区域。风险通常包括内部管理面板暴露、服务端口探测、云环境敏感接口访问、内网服务未授权调用以及敏感响应回显。
审计时应区分“服务端可以发起请求”和“攻击者可以利用该请求访问敏感资源”两个层次。前者是行为特征,后者才是需要重点确认的安全影响。
二、代码审计时优先关注的危险模式
以 Python 为例,下面的代码虽然简短,但将用户输入直接交给网络库,风险边界完全由输入决定:
import requests
def fetch_remote(url):
response = requests.get(url, timeout=10)
return response.content审计时不要只搜索 requests.get。还应关注以下调用链:
- HTTP 客户端:
requests、urllib、httpx、Java 的URL、HttpClient、Go 的http.Get等。 - 间接访问:图片处理器、HTML 转 PDF、截图组件、文档解析器和第三方 SDK。
- 协议处理:
file、ftp、gopher等非预期协议是否被底层库支持。 - 重定向逻辑:请求库是否默认跟随 30x,重定向后的地址是否重新校验。
- 响应处理:是否将响应内容原样返回、写入本地文件,或交给后续解析器。
建议从路由入口开始,沿着“参数接收—URL 解析—DNS 解析—建立连接—跟随重定向—处理响应”的链路逐段确认,而不是只在某个危险函数附近做字符串匹配。
三、URL 校验中最容易出现的误区
1. 只判断字符串前缀
类似 url.startswith(' 的校验并不可靠。合法的 URL 语法允许用户名、端口、路径和编码字符参与解析,单纯的前缀判断无法准确表示目标主机。
2. 只检查一次 DNS 结果
如果应用先解析域名判断是否为公网地址,之后网络库又重新解析一次,就可能出现判断结果与实际连接目标不一致的情况。DNS 记录变化、解析器差异和缓存策略都可能造成这种问题。
3. 只禁止几个常见内网地址
需要同时考虑 IPv4、IPv6、回环地址、链路本地地址、私有地址、保留地址以及 IPv4 映射的 IPv6 地址。仅过滤 127.0.0.1、10.0.0.0/8 等少数网段,容易留下遗漏。
4. 校验原始地址,却信任重定向
即使初始地址是允许的公网域名,目标服务器也可能返回一个指向受限地址的重定向。安全策略应在每一次跳转前重新解析并校验目标,并限制最大跳转次数。
四、一个更稳妥的校验思路
生产环境不建议自行拼装复杂的 URL 黑名单。更可靠的方式是采用“协议白名单、主机白名单、解析后地址校验、连接过程限制”组合策略。
下面是一个用于说明思路的 Python 示例。它不负责下载内容,只展示如何在请求前对目标进行约束。实际项目中还需要结合网络出口策略和统一 HTTP 客户端实现。
from ipaddress import ip_address, ip_network
from urllib.parse import urlparse
import socket
ALLOWED_SCHEMES = {'https'}
ALLOWED_HOSTS = {'static.example.test'}
BLOCKED_NETS = [
ip_network('10.0.0.0/8'),
ip_network('172.16.0.0/12'),
ip_network('192.168.0.0/16'),
ip_network('127.0.0.0/8'),
ip_network('169.254.0.0/16'),
ip_network('::1/128'),
ip_network('fc00::/7'),
ip_network('fe80::/10'),
]
def is_blocked_ip(value):
ip = ip_address(value)
return any(ip in network for network in BLOCKED_NETS) or ip.is_private or ip.is_loopback or ip.is_link_local
def validate_target(raw_url):
parsed = urlparse(raw_url)
if parsed.scheme not in ALLOWED_SCHEMES:
raise ValueError('unsupported scheme')
if parsed.username or parsed.password:
raise ValueError('userinfo is not allowed')
if not parsed.hostname or parsed.port not in (None, 443):
raise ValueError('invalid host or port')
if parsed.hostname.lower() not in ALLOWED_HOSTS:
raise ValueError('host is not allowlisted')
addresses = socket.getaddrinfo(parsed.hostname, 443, type=socket.SOCK_STREAM)
for item in addresses:
resolved = item[4][0]
if is_blocked_ip(resolved):
raise ValueError('resolved address is not allowed')
return parsed这个示例仍然不是完整的通用组件,原因在于 DNS 校验和实际建连之间可能存在时间窗口。因此更推荐以下方案:
- 优先使用固定域名白名单,而不是允许任意公网域名。
- 对允许访问的域名配置固定解析结果,或通过受控代理统一转发。
- 禁止用户指定端口,必要时仅允许 443 或明确的业务端口。
- 禁用自动重定向,或者对每次重定向重新执行完整校验。
- 设置连接超时、读取超时、响应大小上限和最大响应头长度。
- 将发起请求的服务放置在隔离网络中,出口防火墙默认拒绝访问管理网段。
五、修复验证的安全流程
验证 SSRF 修复时,应使用自己控制的测试环境,不要直接探测生产内网或第三方地址。可以准备一个简单的本地 HTTP 服务,用于记录请求方法、路径、请求头和来源地址:
python3 -m http.server 18080 --bind 127.0.0.1然后在测试应用中使用以下几类受控输入验证策略是否生效:
- 合法的白名单 HTTPS 域名,确认正常业务不受影响。
- HTTP、FTP 或其他非白名单协议,确认被拒绝。
- 带用户名密码、非标准端口或异常编码的 URL,确认解析结果符合预期。
- 解析到回环、私有、链路本地和 IPv6 保留地址的测试域名,确认被拒绝。
- 返回重定向的测试服务,确认跳转目标会重新校验。
- 超大响应、慢响应和连接失败场景,确认不会造成资源耗尽。
验证重点不是“某个 Payload 能否成功访问”,而是确认所有不符合业务策略的目标都会在请求发出前被拦截,并且错误信息不会向外暴露内部解析结果、网络拓扑或服务版本。
六、日志与监控同样重要
SSRF 防护不能只依赖代码。建议对服务端主动请求建立结构化日志,但避免记录完整的敏感查询参数。日志至少可以包含请求功能、目标域名、解析地址类别、是否命中策略、失败原因和耗时。
在网络层,可以监控应用服务器是否突然访问管理网段、云平台保留地址或大量非常规端口。对于图片抓取、Webhook 等高频功能,还应设置单用户和单租户的请求速率限制,防止攻击者利用合法功能进行内部端口探测或资源消耗。
七、常见修复失败案例
- 在控制器中做了校验,但另一个异步任务入口没有复用同一套策略。
- 只校验页面展示的 URL,后台下载器仍然直接使用原始参数。
- 过滤了 IPv4 私网段,却遗漏 IPv6 和 IPv4 映射地址。
- 限制了初始 URL,却让 HTTP 客户端自动跟随重定向。
- 使用黑名单维护所有危险地址,随着网络环境变化逐渐失效。
- 应用层校验通过后,容器或主机网络仍可访问不必要的管理服务。
修复时应将策略封装成统一组件,禁止业务代码自行调用底层网络库。对于确实需要访问任意公网资源的场景,可以考虑部署独立的出站代理,在代理层执行域名解析、地址分类、协议限制和审计记录。
总结
SSRF 的核心问题不是某个字符串绕过技巧,而是“用户输入是否能够影响服务端的网络边界”。高质量审计需要同时观察代码调用链、URL 解析行为、DNS 与连接过程、重定向处理、响应使用方式以及底层网络隔离。
实践中,最稳妥的防护组合通常是:默认拒绝、明确的协议和域名白名单、解析后地址校验、禁止或严格控制重定向、限制端口与响应资源,并通过出口防火墙提供最后一道边界。完成修复后,还要使用受控测试环境覆盖 IPv4、IPv6、重定向、超时和异常解析等场景,避免只修复一个入口,却把同样的风险留在其他业务链路中。
推荐意见