
FastGPT SSRF 漏洞修复实战HTTP Tool 出站请求的内网地址防护体系解析【免费下载链接】FastGPTFastGPT is a knowledge-based platform built on the LLMs, offers a comprehensive suite of out-of-the-box capabilities such as data processing, RAG retrieval, and visual AI workflow orchestration, letting you easily develop and deploy complex question-answering systems without the need for extensive setup or configuration.项目地址: https://gitcode.com/GitHub_Trending/fa/FastGPT本文基于 FastGPT 仓库中的 SSRF 漏洞修复设计文档GHSA-6g6x-8hq5-9cw4CWE-918完整还原漏洞成因与攻击面并结合仓库中已落地的修复实现讲清runHTTPTool的 URL 预检、isInternalAddress的分层判定逻辑、CHECK_INTERNAL_IP环境开关的作用边界以及针对 DNS Rebinding 与 302 重定向绕过的安全 axios 拦截器设计。读完后你能掌握一套可直接参考的服务端出站请求 SSRF 防护方案从地址策略、IP 编码变体防御到 DNS 固定pinning与逐跳重定向校验的完整链路。一、漏洞背景HTTP Tool 为何会成为 SSRF 入口设计文档 ssrf-vulnerability-fix.md 记录的漏洞基本信息如下项目内容漏洞编号GHSA-6g6x-8hq5-9cw4漏洞类型Server-Side Request Forgery (SSRF)CWE-918严重程度High影响版本≤ 4.8.22核心问题是 FastGPT 的 HTTP Tool 连接器在处理用户可控的 URL 时缺乏 SSRF 保护。受影响的主要文件为 runHTTPTool 实现 与调用它的 API 端点 runTool.ts。该端点通过authCert校验鉴权后将baseUrl、toolPath等参数直接透传给runHTTPTool。修复前的问题代码形如export const runHTTPTool async ({ baseUrl, toolPath, method, ... }) { const { data } await axios({ method: method.toUpperCase(), baseURL: baseUrl.startsWith(http) ? baseUrl : https://${baseUrl}, url: toolPath, // 没有任何 IP 验证 }); };认证用户可借此向服务端发起任意出站请求设计文档列举了三类典型攻击场景云厂商凭证窃取baseUrl: http://169.254.169.254、toolPath: /latest/meta-data/iam/security-credentials/直接读取 ECS 元数据中的 IAM 临时凭证Kubernetes 密钥泄露baseUrl: http://kubernetes.default.svc、toolPath: /api/v1/namespaces/default/secrets/利用集群内 DNS 发现访问 API Server内网扫描与服务利用以 FastGPT 服务器为跳板探测内网其他主机。除 HTTP Tool 外设计文档还指出了次要问题当时isInternalAddress()依赖CHECK_INTERNAL_IP环境变量默认值为关闭导致 HTTP468 工作流节点和文件读取链路在默认部署下同样缺乏私网段防护。二、修复主线在 runHTTPTool 发起请求前做 URL 预检设计文档的方案 1是在runHTTPTool中于发起请求前验证 URL 是否指向内部地址。当前仓库中该方案已落地见 http.ts 第 183-191 行// Construct full base URL const fullBaseUrl !baseUrl ? : baseUrl.startsWith(http://) || baseUrl.startsWith(https://) ? baseUrl : https://${baseUrl}; // SSRF Protection: Validate URL before making request // When baseUrl is empty, toolPath must be a complete URL const fullUrl fullBaseUrl ? new URL(request.toolPath, fullBaseUrl).toString() : new URL(request.toolPath).toString(); if (await isInternalAddress(fullUrl)) { return { errorMsg: PRIVATE_URL_TEXT }; }相比设计文档中的草案落地实现有两处值得注意的增强兼容空 baseUrl 场景当baseUrl为空OpenAPI 导入的工具直接给出完整 URL时用new URL(request.toolPath)单独解析保证校验对象始终是最终要请求的完整 URL而不是拼出一个畸形地址后校验通过OpenAPI 与手动工具共用校验点apiSchemaStr存在时请求由 buildOpenAPIHttpRequest 从 OpenAPI 原文恢复参数位置手动工具则走buildHttpRequest模板但两者最终都汇聚到同一个 SSRF 校验分支避免任一路径漏检。命中内部地址时返回统一的错误文案PRIVATE_URL_TEXT定义为Request to private network not allowed见 network.ts 第 5 行失败结果以errorMsg形式返回给工作流节点不中断整个会话。三、isInternalAddress分层判定内部地址的核心规则判定逻辑集中在 createInternalAddressChecker由 common/system/utils.ts 注入CHECK_INTERNAL_IP开关后导出。isInternalAddress(url)按以下顺序逐层判定第 146-204 行开发环境放行NODE_ENV development时直接返回false避免本地调试被阻断localhost 与本机主机名localhost以及由HOSTNAME环境变量推导的本机容器名一律拦截云元数据主机名硬编码名单包括metadata.google.internal、metadata、metadata.tencentyun.com、kubernetes.default.svc、kubernetes.default、kubernetes第 24-31 行IP 字面量先经parseHostAsIP归一化再按范围判定普通域名并行执行dns.resolve4/dns.resolve6Promise.allSettled对解析出的每个 IP 做同一套范围判定。范围判定本身基于ipaddr.js凡是addr.range() ! unicast的地址private / loopback / linkLocal / uniqueLocal / reserved / multicast / broadcast / unspecified / carrierGradeNat 等都视为内部地址isInternalIPAddress。此外元数据服务单独强化整个169.254.0.0/16link-local 段、阿里云的100.100.100.200和 AWS IPv6 的fd00:ec2::254均无条件拦截METADATA_IPS 与 isMetadataIPAddress。一个关键细节是无条件项与开关项的分层loopback、unspecified、元数据 IP、localhost、元数据主机名 ——始终拦截与CHECK_INTERNAL_IP无关其余私网段10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、uniqueLocal等—— 仅在CHECK_INTERNAL_IPtrue时拦截。这意味着设计文档中方案 2把CHECK_INTERNAL_IP默认改为拒绝的思路在当前实现中被改造成了更细粒度的形态高危目标回环、元数据默认就拒绝真正的私网段仍由部署方按需开启。CHECK_INTERNAL_IP在 env.ts 第 290 行 中声明为BoolSchema.default(false)描述为是否启用内网 IP 检查。防御 IP 编码变体绕过SSRF 检测最常见的手法是 IP 编码变体绕过十进制2852039166、十六进制0xa9fea9fe、八进制、短点分2130.77、IPv6 映射::ffff:127.0.0.1等。parseHostAsIP 与 parseNumericIPv4 专门处理了这些形态剥离 IPv6 方括号与域名尾部点用ipaddr.process自动把 IPv4-mapped IPv6 解包为 IPv4对ipaddr.js不支持的十进制/十六进制/八进制/1-4 段短写法手写inet_aton兼容解析逐段按进制取值并校验各段上限最终还原成标准点分十进制再判定。这保证了http://0x7f.1/、http://2130706433/之类的变体不会漏过第 3 层。四、DNS Rebinding 防护解析结果复检与 DNS 固定设计文档方案 3提出 DNS rebinding 防护思路解析 → 校验 → 固定已验证的 IP 发起请求。当前仓库在 safe axios 中完整实现了这一思路见 packages/service/common/api/axios.ts二次校验isInternalResolvedIP 专门用于对真正将用于建连的 IP做复检。规则与预检一致元数据 IP 与loopback/unspecified无条件拒绝私网段受CHECK_INTERNAL_IP控制。解析并固定resolveSafeConnectAddress 用dns.lookup(hostname, { all: true, verbatim: true })拿到所有解析结果任何一个命中isInternalResolvedIP即整体拒绝pinned lookupattachPinnedAgent 通过 createPinnedLookup 构造一个自定义http.Agent/https.Agent把 Node 底层建连阶段的 DNS 查询替换为固定返回已验证 IP 的 lookup 函数。这样即使攻击者控制 DNS 在校验之后、建连之前把域名改解析到内网 IPTOCTOU 窗口建连时拿到的仍是校验过的那个地址。这里还有一个工程取舍走代理显式proxy配置或proxy-from-env命中的请求只做本机isInternalAddressbest-effort 检查最终 DNS 解析由代理侧负责只有直连路径会执行 DNS pinning且会覆盖调用方自定义的 agent防止自定义lookup在底层绕过固定逻辑axios.ts 第 136-157 行。五、重定向 SSRF 防护手动接管 3xx 逐跳校验SSRF 的另一经典绕过是首跳合法、302 跳内网。safe axios 通过 addSSRFInterceptor 处理了这个问题请求拦截器把maxRedirects强制置 0关闭 axios/follow-redirects 的底层自动跳转响应拦截器捕获 301/302/303/307/308 状态码解析Location为下一跳绝对 URL 后逐跳重新执行isInternalAddress校验命中即抛出Request to private network not allowed跳转总次数默认上限为SAFE_AXIOS_MAX_REDIRECTS 5超限报错resolveRedirectUrl 限制下一跳协议必须为 http/httpsfile://、gopher://等非 HTTP 协议直接拒绝filterRedirectHeaders 对齐 follow-redirects 的安全取向丢弃Host头、301/302 由 POST 转 GET 时移除content-*头、跨 host/protocol 跳转时移除authorization/cookie/proxy-authorization避免用户配置的密钥被带到新域名。最终导出的 axios 实例 默认带 SSRF 检查另提供axiosWithoutSSRF供明确不需要防护的内部调用使用。runHTTPTool、http468 节点的 fetchData、readFiles等用户可控出站请求都复用了这条链路——http468 甚至在 axios 之外还显式调用了一次isInternalAddress第 551-553 行形成双保险。六、配套能力保存入口的 URL 安全校验除了请求前校验checkUrlSafety 提供保存配置 URL时的统一安全检查必须是合法 URL、协议必须是 http/https、不能指向内部地址。值得注意的是其注释说明由于isInternalAddress在 dev 环境直接放行该函数刻意不依赖isDevEnv用同一套规则做轻量校验让保存入口在开发环境也能拒绝 localhost / metadata 这类明显错误的 URL。七、测试覆盖修复配套了完整的自动化测试与设计文档测试计划章节逐条对应测试文件覆盖内容test/core/app/http.test.tsrunHTTPTool的请求路由、OpenAPI 参数还原、手动工具 Body/Query 行为617 行含 SSRF 相关用例test/common/system/utils.test.tsisInternalAddress对 AWS 元数据端点、Kubernetes 服务、私网段10/172.16/192.168、localhost 与 127.0.0.1 的拒绝以及合法外部 URL 的放行test/common/api/axios.test.tssafe axios 的重定向拦截、代理路径与 DNS pinning 行为test/core/workflow/dispatch/tools/http468.test.tsHTTP468 节点的内部地址拒绝与工作流行为八、向后兼容性与安全建议设计文档对兼容性风险的说明仍然有效开启CHECK_INTERNAL_IPtrue后依赖访问内网服务如内网 API、自建模型服务的 HTTP Tool 与 HTTP468 节点会开始失败。当前实现的默认策略是高危目标恒拒 私网段默认放行、可开关拦截因此升级后若出现内网调用失败有两种处理路径推荐通过网络代理或 API 网关暴露内网服务让 FastGPT 经由代理出站临时保持CHECK_INTERNAL_IPfalse但需接受私网段可被请求的风险仅建议开发环境使用。文档给出的生产环境安全建议可以归纳为四条保持内网 IP 检查开启、在网络层面限制 FastGPT 容器的出站访问范围、对 HTTP Tool 请求做日志与异常模式监控、遵循最小权限原则限制 FastGPT 服务账号云 IAM 角色的权限。小结FastGPT 的这次 SSRF 修复呈现出一个值得参考的分层结构入口层runHTTPTool、http468 节点等每个用户可控出口都在发请求前执行isInternalAddress预检策略层createInternalAddressChecker用无条件拦截回环/元数据/localhost 开关拦截私网段两层策略配合ipaddr.js与inet_aton兼容解析封堵 IP 编码变体传输层safe axios 接管重定向逐跳校验、DNS 复检与 pinning覆盖 DNS Rebinding 与 302 绕过两类 TOCTOU 场景验证层四个测试文件对应设计文档测试计划中的全部用例。这套预检 策略 传输固定 重定向拦截的组合比单纯加一个 IP 白名单更接近生产可用的 SSRF 防护形态对任何允许用户输入 URL 的服务端项目都有直接借鉴价值。【免费下载链接】FastGPTFastGPT is a knowledge-based platform built on the LLMs, offers a comprehensive suite of out-of-the-box capabilities such as data processing, RAG retrieval, and visual AI workflow orchestration, letting you easily develop and deploy complex question-answering systems without the need for extensive setup or configuration.项目地址: https://gitcode.com/GitHub_Trending/fa/FastGPT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考