SSRF(Server-Side Request Forgery,服务器端请求伪造)并不是单一的参数校验问题,而是应用在处理外部 URL、回调地址、文件导入、图片抓取和 Webhook 时,缺少网络边界控制所形成的一类风险。它的危险性通常不在于请求本身,而在于请求由服务器发起后,继承了服务器所在网络的访问能力。在实际审计中,SSRF 往往和其他问题叠加出现:应用服务器可以访问内部管理接口,云环境提供实例元数据服务,代理层会自动跟随重定向,或者 DNS 解析结果在校验与连接之间发生变化。因此,单纯增加一个 startsWith(' 或屏蔽几个字符串,通常不能形成可靠防护。一、常见业务场景与审计入口首先应当从数据流而不是关键词出发。凡是用户能够影响目标地址,并且服务端会发起网络请求的功能,都值得重点检查。图片、头像、远程附件或 Markdown 图片的服务端下载。Webhook、回调地址、第三方通知和接口连通性检测。PDF、网页快照、网页转图片等服务端渲染功能。通过 URL 导入配置、RSS、视频封面或远程字典。跳转服务、代理接口、在线抓取和 URL 预览。微服务之间根据请求参数动态选择下游服务的场景。审计时可以沿着以下链路追踪:用户输入 URL
-> URL 解析与规范化
-> 域名解析
-> 建立 TCP/TLS 连接
-> 处理重定向
-> 读取响应
-> 解析 XML、JSON、HTML 或文件内容
-> 返回给用户或写入内部系统链路中的每一步都可能改变安全边界。例如,最初校验的是域名,但最终连接的可能是另一个地址;最初只允许一个域名,服务端却自动跟随到了一个未经检查的重定向目标;响应本身看起来只是文本,后续解析器却又触发了新的外部请求。二、漏洞成因:校验对象和实际连接对象不一致SSRF 防护最容易出现的错误,是只验证字符串,而没有验证最终的网络连接。下面是一些常见但不可靠的做法:仅判断 URL 是否以 http:// 或 https:// 开头。仅禁止字符串中的 localhost、127.0.0.1 等少量关键词。解析一次域名,判断结果为公网地址后,交给另一个 HTTP 客户端再次解析。只校验初始 URL,不校验 30x 重定向后的地址。使用黑名单阻止少数内网网段,却没有覆盖 IPv6、环回地址、链路本地地址和保留地址。允许 URL 中携带用户信息、非标准端口或复杂编码,却没有统一规范化处理。更稳妥的思路是建立明确的出站访问策略:先解析并规范化 URL,再根据业务需求限制协议、端口、域名和解析结果;建立连接时仍然要保证使用的是经过校验的地址;重定向、DNS 重新解析和后续解析器也必须处于同一套策略约束下。三、审计中应重点检查的技术细节1. URL 解析差异不同语言和库对 URL 的解析规则并不完全一致。审计时不要只看正则表达式,需要确认程序最终拿到的主机名、端口、协议和路径分别是什么。尤其要关注:用户名和密码字段是否影响主机名判断。显式端口是否被允许,默认端口是否被隐式替换。大小写、百分号编码、Unicode 域名和尾随点的处理是否一致。是否支持 IPv6 字面量,以及 IPv4 映射地址。解析前后的主机名是否经过同样的规范化。在代码审计中,建议把 URL 解析结果记录为结构化字段,而不是在原始字符串上进行多次截取。例如至少单独获得 scheme、hostname、port 和 path,并拒绝无法明确解析的输入。2. DNS 解析与连接之间的竞态如果程序先调用 DNS 解析接口进行安全判断,随后又把原始域名交给 HTTP 客户端建立连接,就会出现校验结果和实际连接结果不一致的问题。DNS 记录可能变化,解析器也可能使用不同的缓存、代理或网络出口。工程上更可靠的方式包括:由统一的出站代理执行 DNS 和网络策略;或者由网络层将目标解析、策略判断和连接建立放在同一个受控组件中。对高风险服务,不建议由业务代码自行拼装一套不完整的 DNS 防护逻辑。3. 重定向处理自动跟随重定向是 SSRF 中最容易遗漏的路径。初始地址可能符合白名单,但响应中的 Location 指向了另一个协议、端口或网络区域。建议默认关闭自动重定向;如果业务确实需要,则应对每一次跳转重新执行完整校验,并设置最大跳转次数。4. IPv4、IPv6 和特殊地址范围防护策略不应只覆盖常见的 IPv4 私有地址。至少应根据语言标准库判断以下类别:环回地址和未指定地址。RFC 1918 私有地址。链路本地地址、组播地址和保留地址。IPv6 唯一本地地址、环回地址和链路本地地址。企业自定义内网网段及云平台内部服务网段。需要注意的是,是否允许访问私网不能脱离业务讨论。某些内部服务本来就需要访问指定的内网 API,这时应采用明确的域名或地址白名单,而不是简单地把所有私网地址加入允许列表。四、一个偏防御性的校验示例下面的 Python 代码只展示审计和设计时需要考虑的检查项,不应直接当作生产级网络隔离方案。它负责识别明显不应访问的目标,但并不能解决校验与连接之间的竞态,也不能替代出口防火墙或代理策略。from urllib.parse import urlparse
import ipaddress
import socket
ALLOWED_SCHEMES = {'http', 'https'}
ALLOWED_PORTS = {80, 443}
def is_public_address(address):
ip = ipaddress.ip_address(address)
return not (
ip.is_private or
ip.is_loopback or
ip.is_link_local or
ip.is_multicast or
ip.is_unspecified or
ip.is_reserved
)
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:
raise ValueError('hostname is required')
port = parsed.port or (443 if parsed.scheme == 'https' else 80)
if port not in ALLOWED_PORTS:
raise ValueError('port is not allowed')
host = parsed.hostname.rstrip('.')
addresses = socket.getaddrinfo(host, port, type=socket.SOCK_STREAM)
ips = {item[4][0] for item in addresses}
if not ips or not all(is_public_address(item) for item in ips):
raise ValueError('target is not allowed')
return {
'scheme': parsed.scheme,
'host': host,
'port': port,
'path': parsed.path or '/',
'query': parsed.query
}这个示例有几个值得注意的地方:协议和端口采用白名单;禁止用户信息字段;对所有解析结果进行判断,而不是只取第一个地址;通过标准库识别地址类型,而不是手工维护少量字符串规则。但它仍有明显边界:DNS 结果可能在后续连接时变化,底层 HTTP 库可能重新解析域名,代理环境也可能改变实际出口。因此生产环境更推荐将请求交给统一的 egress proxy,在代理侧实施域名、地址、端口和响应大小限制,并让应用无法直接访问任意网络。五、响应处理同样需要限制SSRF 不只体现在是否能发出请求,响应处理也可能造成新的风险。设置连接超时、读取超时和整体请求超时,避免形成慢连接资源消耗。限制响应体大小,避免下载超大文件导致内存或磁盘耗尽。限制最大重定向次数,并对每个目标重新校验。不要默认执行响应中的脚本、模板、宏或外部实体。对 XML 解析器关闭外部实体和外部 DTD,避免 SSRF 与 XML 外部实体问题叠加。根据业务需要限制响应类型,不要让任意二进制内容进入后续解析链路。日志中记录规范化后的目标、解析结果、重定向链和拒绝原因,但避免记录敏感认证信息。六、如何在测试环境中验证修复验证时建议使用隔离的本地实验环境,不要对不属于自己的内网、云服务或第三方系统进行探测。可以准备一个本地 Mock 服务,分别返回普通响应、重定向响应、大响应和异常响应,然后检查业务服务是否按预期处理。测试用例一:允许的 HTTPS 域名和标准端口
预期:请求成功,日志记录规范化后的目标。
测试用例二:不允许的协议或非白名单端口
预期:在建立连接前拒绝。
测试用例三:域名解析得到多个地址,其中一个属于受限地址
预期:整体拒绝,而不是只使用第一个结果。
测试用例四:初始地址允许,但重定向目标不允许
预期:重定向前重新校验并拒绝。
测试用例五:响应超过大小限制或读取超时
预期:中断读取并释放连接。
测试用例六:URL 含有用户名、密码、异常端口或无法解析的主机名
预期:统一返回参数错误,不进入网络请求阶段。如果系统通过代理访问外部资源,还要分别验证应用直连是否被网络策略阻断、代理是否强制执行策略、IPv4 和 IPv6 是否走同一出口,以及代理对重定向的处理是否一致。仅在应用层测试通过,并不能说明真实网络边界已经生效。七、代码审计记录建议为了避免遗漏,可以在审计报告中按数据流记录以下内容:检查项需要确认的问题常见修复方向输入来源URL 是否可被用户直接或间接控制缩小输入范围,使用业务对象而非任意 URL解析逻辑是否统一解析协议、主机、端口和路径使用标准解析器,拒绝歧义输入网络策略是否允许访问内网、环回和特殊地址出口代理、网络 ACL 和明确白名单重定向每次跳转是否重新校验关闭自动跳转或限制跳转次数并复检响应处理是否限制大小、类型和读取时间超时、大小上限、类型白名单和安全解析器可观测性是否能定位拒绝原因和实际出口结构化日志、指标和告警八、总结SSRF 的核心不是找到一个特殊写法,而是确认服务端是否把用户可控的数据带入了出站网络请求。有效防护也不是简单增加黑名单,而是让 URL 解析、DNS、连接、重定向和响应处理始终遵守同一套安全策略。在实际项目中,优先级通常可以这样安排:先减少业务对任意 URL 的需求;再使用域名或资源标识白名单;将出站流量统一收敛到代理或受控网络出口;最后在应用层补充协议、端口、地址、重定向、超时和响应大小校验。对云环境还应结合身份权限、元数据服务隔离和网络 ACL 做纵深防御。这样即使某一层校验出现遗漏,也不至于直接扩大为整个内网或云环境的访问风险。
推荐意见