← 中文资源

简体中文版本

SSRF — 服务端访问内部或元数据 URL

通过按白名单校验 URL、阻断私有与链路本地网段,并禁止跳转到内部主机,缓解 SSRF。

阅读英文原文

症状

遇到“SSRF — 服务端访问内部或元数据 URL”时靠猜测只会浪费时间。请按证据排查:先复现症状,再执行针对性诊断,定位根因后修复,并用明确检查验证结果。

可观察到的症状包括:

  • 接收用户 URL 的抓取端点可访问 169.254.169.254
  • 扫描报告 SSRF,或应用返回内部服务响应
  • 从允许主机的跳转可到达内部地址

诊断

先在隔离环境中复现故障,再按顺序执行以下检查,逐步缩小范围。

步骤 1

curl -i "https://api.example.com/fetch?url=http://169.254.169.254/latest/meta-data/"

成功返回元数据内容即确认 SSRF;被阻断则说明已有缓解。

步骤 2

grep -rn "fetch(\|axios\|http.get\|requests.get" src/ | head

每一处由用户输入驱动的出站抓取都需要白名单与私有网段检查。

常见诱因(按出现频率排序):

  • 服务端未经校验就抓取用户提供的任意 URL
  • 仅检查初始主机,跳转可到达私有网段
  • DNS 解析把允许的名称指向内网 IP(DNS 重绑定)

根因

当服务端向攻击者控制的目标发起请求(通常来自 URL 参数)时即产生 SSRF,使攻击者可达内部服务、云元数据端点,或绕过网络控制。

修复

使用严格的主机与 scheme 白名单,解析主机并拒绝私有、回环与链路本地 IP 段,在每次跳转后重新检查,并为出站抓取使用专用出口代理。

注意:仅检查主机名不足以防御 DNS 重绑定;应校验解析后的 IP 并固定连接。

import { lookup } from 'node:dns/promises';
import net from 'node:net';

const ALLOWED = new Set(['api.partner.com', 'cdn.example.com']);

function isPrivate(ip) {
  return /^(127\.|10\.|169\.254\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/.test(ip)
    || ip === '::1' || ip.startsWith('fc') || ip.startsWith('fd');
}

export async function safeFetch(rawUrl) {
  const url = new URL(rawUrl);
  if (url.protocol !== 'https:' || !ALLOWED.has(url.hostname)) throw new Error('blocked host');
  const { address } = await lookup(url.hostname);
  if (isPrivate(address) || net.isIP(address) === 0) throw new Error('blocked address');
  return fetch(url, { redirect: 'manual' }); // do not auto-follow into private ranges
}

验证

重发内部与元数据探测并确认被阻断;验证白名单内的公网 URL 仍可抓取。

修复后补充一条回归测试,复现原始症状,防止问题再次出现。

这类故障通常是环境问题而非纯逻辑错误,因此在缺陷记录中应写明完整命令、环境变量与工作目录。请用最小的脚本复现,待最小用例清楚后再扩大范围。为这类故障准备一份简短的排查清单,能显著减少下次排障时间。