跳转到帖子

SSRF(Server-Side Request Forgery,服务端请求伪造)并不只是一个“能让服务器发起请求”的问题。真正影响风险的,通常是请求目标是否可控、服务器所在网络能访问哪些资源、应用是否跟随重定向,以及响应内容是否会回显给用户。本文以代码审计和本地实验为主线,整理一套适合企业 Web 应用的 SSRF 识别、验证与修复方法。一、问题背景与常见场景SSRF 常出现在需要由后端访问外部地址的功能中,例如图片远程导入、Webhook、在线截图、文档转换、URL 预览、第三方接口代理和云资源同步。典型代码逻辑是:用户提交一个 URL,服务端使用 HTTP 客户端访问该地址,再将状态码、响应内容或处理结果返回给用户。风险通常来自以下几个方面:攻击者可以控制完整 URL,服务端缺少协议、域名和端口限制。应用所在网络能够访问仅对内开放的管理接口、数据库面板或内部服务。服务端将响应正文直接返回,导致内部信息泄露。应用允许自动跟随重定向,初始校验通过后又访问了不可信目标。仅使用字符串黑名单拦截内网地址,容易被不同格式的 IP 表示绕过。二、代码审计时的定位思路审计时不要只搜索 SSRF 关键词,更有效的方法是从“用户输入如何进入网络请求”这条数据流入手。可以重点关注以下调用点:Java 中的 URL、HttpURLConnection、Apache HttpClient、OkHttp。Python 中的 requests、urllib、aiohttp。Node.js 中的 axios、fetch、http.request。Go 中的 net/http.Client、http.Get 和自定义 Transport。图像、PDF、网页截图等组件中隐式发起网络请求的功能。审计时建议记录四个关键问题:目标地址来自哪里;是否限制了协议;域名解析后的 IP 是否经过校验;重定向、代理和 DNS 解析是否仍在安全边界内。只看到一个 HTTP 请求调用,并不能直接判断漏洞是否成立,必须沿着完整数据流分析校验逻辑。三、容易被忽略的校验细节很多修复方案只判断 URL 字符串是否包含 127.0.0.1、localhost 或某些内网网段,这种方式可靠性较低。实际校验应当基于解析后的主机地址,并同时考虑 IPv4、IPv6、IPv4-mapped IPv6、十进制或十六进制 IP 表示,以及域名解析结果。此外,DNS 解析和真正建立连接之间可能存在时间窗口。如果校验阶段解析到公网地址,而建立连接阶段重新解析后得到内网地址,就可能形成 DNS rebinding 风险。因此,生产环境不应只依赖应用层字符串判断,还应结合出口代理、防火墙和网络分段控制。重定向也是常见遗漏点。第一跳地址可能是允许访问的公网域名,但服务端自动跟随 301 或 302 后访问了受限网段。对于需要严格控制目标的场景,建议默认关闭自动重定向;如果业务必须支持,应对每一跳重新执行协议、端口、域名和 IP 校验。四、在本地环境进行安全验证验证时应使用自建的本地测试服务,不要探测不属于自己的内部系统。可以启动一个简单的监听服务,用于确认请求是否由目标应用发出:python3 -m http.server 18080 --bind 127.0.0.1然后在测试应用中提交仅指向本机实验服务的 URL,例如: 127.0.0.1、IPv6 回环地址和内网保留地址时是否被阻断。重定向到受限地址时是否会被再次校验。如果应用没有响应正文回显,也不能直接认为不存在 SSRF。盲 SSRF 仍可能通过 DNS 日志、测试服务访问日志或应用自身的请求审计日志确认。验证过程中应只使用无敏感内容的自建服务,并保留时间、请求路径和应用日志,方便复盘。五、一个相对稳妥的防护实现防护应当采用白名单思路,而不是持续补充黑名单。理想情况下,业务只允许访问明确登记的域名、协议和端口,并通过统一出口代理转发。下面给出一个简化的 Python 校验示例,重点展示校验顺序,生产环境还应结合网络策略和连接层控制:from ipaddress import ip_address, ip_network
from urllib.parse import urlparse
import socket

PRIVATE_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_private_or_reserved(value):
address = ip_address(value)
return any(address in network for network in PRIVATE_NETS)

def validate_target(raw_url):
parsed = urlparse(raw_url)

if parsed.scheme not in ('http', 'https'):
raise ValueError('unsupported scheme')

if parsed.username or parsed.password:
raise ValueError('userinfo is not allowed')

if not parsed.hostname:
raise ValueError('missing hostname')

port = parsed.port or (443 if parsed.scheme == 'https' else 80)
if port not in (80, 443):
raise ValueError('unsupported port')

addresses = socket.getaddrinfo(parsed.hostname, port, type=socket.SOCK_STREAM)
for item in addresses:
ip_value = item[4][0]
if is_private_or_reserved(ip_value):
raise ValueError('target address is not allowed')

return parsed这段代码只能作为审计和设计参考,不能单独当作完整防线。实际实现中还需要避免解析后重新按域名连接,尽量将经过校验的地址绑定到连接过程;对于代理架构,要明确代理本身是否会再次解析域名;同时限制响应大小、连接超时、读取超时和最大重定向次数。六、工程化修复建议协议采用白名单,通常只允许 http 和 https,禁止 file、gopher、ftp 等非业务协议。域名采用业务白名单,必要时只允许固定后缀并进行精确匹配,避免 example.com.evil.test 之类的误判。限制端口范围,避免让通用 HTTP 客户端访问任意服务端口。在解析后检查所有 IP 地址,覆盖 IPv4、IPv6、回环、链路本地、组播、保留和私有网段。默认关闭自动重定向,业务需要时对每一跳重新校验。设置连接超时、读取超时、响应体大小和并发限制,降低资源消耗风险。通过网络出口代理统一控制外连,并在防火墙层禁止应用服务器直连敏感管理网段。记录目标域名、解析结果、最终连接地址、状态码、耗时和拦截原因,但不要把完整响应正文写入普通业务日志。七、常见错误修复方式第一种错误是只在前端限制 URL。前端校验可以改善交互体验,但不能替代服务端校验,任何请求都可能绕过浏览器直接提交。第二种错误是仅判断字符串中的 localhost 或 127.0.0.1。应当对 URL 进行规范解析,并对解析结果进行 IP 层判断。第三种错误是先校验域名,之后让底层 HTTP 库自由处理重定向和 DNS。安全边界必须覆盖整个请求生命周期,而不是只覆盖第一跳。第四种错误是修复后没有回归测试。建议将协议、端口、内网地址、IPv6 地址、域名解析异常、重定向和超时等场景加入自动化测试,避免后续更换 HTTP 客户端或代理配置时重新引入问题。八、总结SSRF 的核心不是某个特殊字符串,而是“不受信任的目标地址进入了服务端网络请求能力”。可靠的分析方法是沿数据流定位请求点,确认协议、主机、端口、解析、重定向和响应处理,再结合本地服务完成最小化验证。可靠的修复则应以白名单、IP 层校验、重定向控制、出口网络隔离和持续回归测试为基础。对于安全团队,建议把 SSRF 检查纳入代码审计清单和上线前测试;对于开发团队,最好将安全请求封装成统一组件,避免每个业务模块自行实现 URL 校验。这样不仅能减少单点遗漏,也便于后续集中更新策略和审计日志。

代码审计与漏洞定位示意图
代码审计、调用链与关键函数定位示意

0篇意见

推荐意见

没有意见。

游客
抱歉,你的帖子内容包括我们不允许的字词。请编辑你的帖子,删除下面高亮的屏蔽字。
添加意见…