SSRF(Server-Side Request Forgery,服务端请求伪造)并不是“让服务器发一个请求”这么简单。真正难点通常在于:请求入口分散在业务代码中,URL 经过多次解析和拼接,网络层又可能存在代理、DNS、IPv6 或云元数据服务等差异,最终导致看似严格的校验在某个环节失效。本文以代码审计和安全验证为主线,整理一套适用于授权测试与内部代码评审的分析方法。示例均以本地测试环境为前提,不涉及对第三方系统、内网资产或云环境的未授权探测。一、典型业务场景常见的 SSRF 入口包括以下功能:根据用户提交的 URL 抓取网页标题、生成预览或下载远程文件。Webhook、回调地址、第三方接口配置。图片、头像、文档的远程导入。PDF、Markdown、网页转码等服务端渲染功能。运维平台中的连通性检查、健康检查或代理请求。一个典型的危险代码可能如下:const target = req.body.url;
const response = await fetch(target);
const body = await response.text();
res.send(body);问题不在于 fetch 本身,而在于服务端是否允许用户完全控制请求目标、协议、端口、重定向以及响应内容。如果这些边界没有明确限制,攻击者可能借助应用服务器访问本不应由外部用户直接访问的资源。二、审计时先画出完整数据流不要一上来只搜索 requests.get、fetch 或 curl。更有效的做法是从用户可控输入出发,沿着数据流追踪到最终的网络请求。定位输入点:HTTP 参数、JSON 字段、数据库配置、消息队列内容、管理后台表单等。确认传播路径:是否经过 URL 拼接、解码、规范化、模板渲染或异步任务。定位请求函数:HTTP 客户端、代理组件、浏览器自动化框架、文件下载库。检查响应去向:响应是直接返回给用户,还是写入文件、解析 XML、转换成图片或交给其他服务。记录安全边界:协议、主机、端口、重定向次数、DNS 解析时机以及出站网络策略。建议在审计笔记中维护一张简单的数据流表:环节需要确认的问题常见风险输入是否可被普通用户控制参数污染、配置注入解析是否只解析一次,是否发生解码编码绕过、解析差异校验校验的是字符串还是最终连接地址域名与 IP 不一致请求是否自动跟随重定向校验后跳转到受限地址响应是否返回或解析服务端响应敏感信息泄露、二次解析三、重点分析 URL 校验的失效方式1. 只做字符串黑名单例如仅禁止 localhost、127.0.0.1 或某几个内网网段。这类方式维护成本高,而且容易受到大小写、编码、IPv6 表示方式以及解析差异的影响。黑名单可以作为补充,但不应作为主要防线。2. 校验域名,连接时却重新解析安全代码需要关注 DNS 解析的时间点。如果校验阶段解析出的地址与实际连接阶段得到的地址不同,就可能出现典型的 DNS rebinding 问题。即使业务使用的是域名白名单,也应确保最终建立连接的 IP 仍然满足网络策略。3. 允许重定向导致边界被绕过初始 URL 可能属于允许访问的站点,但服务端跟随 30x 重定向后进入了不应访问的地址。防御上应默认关闭自动重定向,或者对每一次跳转重新执行协议、主机、端口和 IP 校验。4. 解析器与网络库行为不一致URL 解析库、代理组件和底层 socket 可能对用户名密码、端口、IPv6、特殊字符的处理不同。审计时应确认“校验所使用的解析结果”和“实际请求所使用的连接目标”是否来自同一套规范化逻辑。四、推荐的安全校验流程一个相对稳妥的流程是:先限制协议,再规范化 URL,解析主机,解析全部 DNS 结果,校验每个地址,最后使用已校验的连接目标发起请求。只允许业务确实需要的协议,例如仅允许 https,不要默认支持 file、ftp、gopher 等协议。限制端口,通常只允许 443 或业务明确声明的端口。拒绝用户名密码、空主机、非标准端口和不必要的 URL 特性。将域名解析为全部 A 和 AAAA 记录,对每个地址执行内网、回环、链路本地、保留地址等检查。关闭自动重定向;如果业务必须支持,则每次跳转都重新校验。限制响应大小、连接超时、读取超时和总耗时,避免被用于资源消耗。将出站网络策略作为最后一道边界,应用容器不应默认访问整个内网。下面是一个仅用于说明思路的 Python 校验示例。生产环境还需要结合实际 HTTP 客户端,确保解析结果与连接过程不会重新回到未经校验的域名解析流程。from ipaddress import ip_address
from urllib.parse import urlsplit
import socket
ALLOWED_SCHEMES = {
推荐意见