尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

4.9 SSRF 防护实战:WebFetch 出站安全与私有 IP 拦截配置

发布时间:2026/9/29 6:55:48

资讯中心
01
ARTICLE

4.9 SSRF 防护实战:WebFetch 出站安全与私有 IP 拦截配置

4.9 SSRF 防护实战:WebFetch 出站安全与私有 IP 拦截配置
1. 从一次内网探测说起WebFetch 出站请求为什么必须拦WebFetch 类工具在服务端发起出站请求时最大的风险不是抓不到网页而是抓到了不该抓的东西。SSRFServer-Side Request Forgery服务端请求伪造的本质是攻击者通过控制请求目标让服务端代替自己去访问内网资源。放到 Agent 场景里这个风险被放大——因为发起请求的可能是被 Prompt 注入影响的模型而不是一个明确知道自己在干什么的开发者。我见过最典型的链路是这样的项目 README 里藏了一段指令诱导 Agent 用 WebFetch 去请求http://169.254.169.254/latest/meta-data/iam/security-credentials/。这个地址是云厂商的元数据端点一旦返回内容被模型读进上下文IAM 临时凭证就可能顺着后续的对话或日志外泄。更隐蔽的是攻击者不需要直接写死内网 IP用::ffff:169.254.169.254这种 IPv4-mapped IPv6 写法或者用一个先解析到公网、再解析到内网的域名DNS Rebinding就能绕过只检查字符串的黑名单。所以这篇要解决的核心问题是在自有环境里怎么给 WebFetch 这类出站工具加一层可落地的私有 IP 拦截与出站安全策略。适合谁正在把 Agent 接入生产、需要过安全评审的后端和平台同学以及自己写 HTTP Hook、担心配置被恶意篡改的开发者。下面我会给出可复制的 HTTP Hook 拦截配置骨架、settings.json片段并用内网地址实测拦截是否生效。整套思路围绕四个关键词SSRF、WebFetch、私有 IP 拦截、出站安全。2. 前置准备TaoToken 接入与出站策略的边界在写拦截逻辑之前先把请求从哪出去这件事理清楚。出站安全的第一原则是所有出站请求都应该经过一个你能观测、能拦截的统一入口。如果你用的是 TaoToken 这类统一接入层模型调用和工具出站可以走同一套凭证与审计体系排查问题时不用在多个服务之间来回跳。TaoToken 的接入地址是https://taotoken.net/api控制台和密钥管理在官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。你需要先拿到 API Key再把它配到环境变量里后续的 Hook 和 WebFetch 都复用这套凭证。# 写入环境变量避免硬编码到配置文件 export TAOTOKEN_API_KEYsk-你的密钥 export TAOTOKEN_BASE_URLhttps://taotoken.net/api密钥申请入口在 API Keys 管理页接入细节可以对照接入文档。这里要强调一个边界TaoToken 负责的是模型与工具调用的统一接入不替代你自己的出站防火墙。私有 IP 拦截必须落在你自己的服务端或 Hook 层因为只有你知道哪些内网段是敏感的。注意不要把 API Key 写进会被提交到 Git 的settings.json。用环境变量引用并在 Hook 里做变量白名单只允许特定变量被插值。3. 可复制配置HTTP Hook 拦截骨架与 settings.json出站拦截最实用的落点是 HTTP Hook——它在请求真正发出前介入能拿到目标 URL 和解析后的地址。下面是一个拦截骨架核心逻辑是解析目标主机 → 判断是否为私有/链路本地地址 → 命中则直接拒绝。// ssrf-guard-hook.js —— 出站请求前的私有 IP 拦截 const dns require(dns).promises; const net require(net); // 私有/保留地址段命中即拦截 const BLOCKED_V4 [ [/^0\./, this-network], [/^10\./, rfc1918-10], [/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./, cgnat], [/^169\.254\./, link-local], [/^172\.(1[6-9]|2\d|3[01])\./, rfc1918-172], [/^192\.168\./, rfc1918-192], ]; function isBlockedV4(ip) { return BLOCKED_V4.find(([re]) re.test(ip))?.[1] || null; } function isBlockedV6(ip) { const lower ip.toLowerCase(); if (lower ::1) return null; // 回环放行本地开发需要 if (lower ::) return unspecified; if (lower.startsWith(fc) || lower.startsWith(fd)) return unique-local; if (/^fe[89ab]/.test(lower)) return link-local-v6; // IPv4-mapped IPv6 递归降维 const m lower.match(/^::ffff:(\d\.\d\.\d\.\d)$/); if (m) return isBlockedV4(m[1]); return null; } async function guard(urlStr) { const u new URL(urlStr); const host u.hostname; // 主机名是 IP 字面量直接判定 if (net.isIP(host)) { const reason net.isIPv4(host) ? isBlockedV4(host) : isBlockedV6(host); if (reason) throw new Error(SSRF blocked: ${host} (${reason})); return; } // 域名解析所有结果任一命中即拦截 const addrs await dns.lookup(host, { all: true }); for (const { address } of addrs) { const reason net.isIPv4(address) ? isBlockedV4(address) : isBlockedV6(address); if (reason) throw new Error(SSRF blocked: ${host} - ${address} (${reason})); } } module.exports { guard };对应的settings.json片段把 Hook 挂到出站事件上并限制环境变量插值范围{ hooks: { PreToolUse: [ { matcher: WebFetch, hooks: [ { type: command, command: node ./hooks/ssrf-guard-hook.js } ] } ] }, allowedHttpHookUrls: [https://your-audit.internal/*], httpHookAllowedEnvVars: [TAOTOKEN_API_KEY, TAOTOKEN_BASE_URL] }几个关键点值得展开。allowedHttpHookUrls是 Hook 自身的出站白名单undefined表示不限制、空数组表示全部阻止、非空数组则必须匹配某个模式——生产环境建议显式列出审计端点。httpHookAllowedEnvVars控制哪些环境变量能被插值进 Hook 的 header不在白名单里的变量会被替换为空字符串防止密钥被恶意配置外泄。另外Hook 的 header 值一定要做 CRLF 清洗剥离\r、\n、\0否则token\r\nX-Evil: 1这种值会注入额外 header。4. 验证请求用内网地址实测拦截是否生效配置写完不验证等于没写。下面用几个典型地址实测覆盖 IPv4 私有段、链路本地、IPv4-mapped IPv6 三种情况。// verify-guard.js const { guard } require(./ssrf-guard-hook); const cases [ http://169.254.169.254/latest/meta-data/, // 云元数据应拦截 http://10.0.0.5/admin, // RFC1918应拦截 http://192.168.1.1/router, // RFC1918应拦截 http://[::ffff:169.254.169.254]/meta, // IPv4-mapped应拦截 http://127.0.0.1:8080/health, // 回环应放行 https://example.com/, // 公网应放行 ]; (async () { for (const url of cases) { try { await guard(url); console.log(PASS ${url}); } catch (e) { console.log(BLOCK ${url} - ${e.message}); } } })();预期输出大致如下BLOCK http://169.254.169.254/latest/meta-data/ - SSRF blocked: 169.254.169.254 (link-local) BLOCK http://10.0.0.5/admin - SSRF blocked: 10.0.0.5 (rfc1918-10) BLOCK http://192.168.1.1/router - SSRF blocked: 192.168.1.1 (rfc1918-192) BLOCK http://[::ffff:169.254.169.254]/meta - SSRF blocked: ::ffff:169.254.169.254 (link-local) PASS http://127.0.0.1:8080/health PASS https://example.com/如果::ffff:169.254.169.254这一条没被拦住说明你的实现只做了 IPv4 字符串匹配漏掉了 IPv4-mapped IPv6 的递归降维。这是最常见的绕过点务必单独测。回环地址放行是有意为之——本地开发、策略服务器、健康检查都依赖它一刀切会打断正常工作流。验证通过后可以进一步用真实模型对话确认整条链路在 模型对话 里让模型尝试请求一个内网地址观察 Hook 是否在请求发出前就返回了拦截信息。这一步能验证 Hook 与工具调用链的挂载是否正确。5. 本篇常见错排查拦截没生效请求照样出去了。先确认 Hook 的matcher是否精确匹配到工具名。不同版本的工具体系里WebFetch 可能被归类到外部集成类工具matcher 写错就完全不会触发。其次检查 Hook 脚本的退出码——非零退出才会阻断如果脚本内部 catch 了异常却没重新抛出进程正常退出请求就放行了。域名解析到多个 IP只拦了第一个。这是典型的漏网场景。dns.lookup默认只返回一个地址必须显式传{ all: true }拿到全部结果任一命中私有段就拦截。否则攻击者可以让域名同时解析到公网和内网你检查到公网那个就放行了。DNS Rebinding 窗口没堵住。如果你的实现是先解析验证、再让 HTTP 客户端自己解析连接两次解析之间就存在时间窗口。正确做法是把验证后的 IP 直接交给连接层使用让验证和连接用同一个 IP消除 TOCTOU 竞争。上面骨架里guard只做验证实际接入时要把解析结果透传给请求库的lookup选项。代理环境下拦截误伤。如果系统配了 HTTP 代理代理地址本身常在私有段如10.0.0.1:3128会被 Guard 误拦导致所有出站都失败。这时要么让代理层承担访问控制、Guard 对代理连接退让要么把代理地址加入例外。代理激活时 Guard 验证的是代理 IP 而非目标 IP验证结果本身也失去意义退让是合理选择。环境变量插值把密钥带出去了。检查httpHookAllowedEnvVars是否配了最小集合。默认不配的话某些实现可能允许任意变量插值恶意 Hook 配置就能把AWS_SECRET_ACCESS_KEY之类塞进 header 发到外部。白名单 CRLF 清洗两层都要有。6. 把出站安全固化进日常流程拦截配置写完只是起点真正难的是让它长期有效。我的做法是把上面那段验证脚本挂进 CI每次改 Hook 或settings.json都跑一遍用例里固定包含169.254.169.254、::ffff:映射、多 IP 域名这几类。这样任何一次顺手放宽都会被测试挡下来。如果你还在搭 Agent 的编码工作流出站安全建议和权限体系一起设计别等上线前才补。长期跑编码任务或 Agent 的话可以用 Coding Plan 把模型调用和工具出站统一到一套凭证下审计时能一眼看到哪个会话请求了哪些地址。密钥轮换和用量查看在 控制台接入细节随时对照接入文档。最后留一个实用习惯每次新增预批准域名或放宽白名单时问自己一句这个域名允许上传吗。允许文件上传的站点一旦被放进任意网络访问的白名单就会变成数据外泄通道——GET 请求安全不代表 POST 也安全。这条边界想清楚出站安全就稳了一大半。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。