uri\rfc3986\uri 类仅负责uri解析与规范化,不校验安全性;防ssrf需手动实施协议白名单、真实dns解析主机、端口限制及规范化后语义比较。

Uri\Rfc3986\Uri 类不是 SSRF 防御的“利器”,它只负责正确解析和规范化 URI,不拦截、不校验、不拒绝请求——防 SSRF 还得靠你自己写逻辑。
为什么不能用 Uri::createFromString() 直接防 SSRF?
Uri\Rfc3986\Uri 解析成功,不代表这个 URL 安全;它甚至不会检查 file:// 或 http://127.0.0.1。它的作用是:把输入变成一个结构清晰、可编程操作的对象,仅此而已。
- 输入
"file:///etc/passwd"→ 解析成功,$uri->getScheme()返回"file",$uri->getPath()返回"/etc/passwd" - 输入
"http://[::1]/admin"→ 正确识别 IPv6 主机,但不判断是否为私有地址 - 输入带重定向跳转的 URL(如含
Location: http://10.0.0.5的响应)→Uri类完全不感知网络行为,只处理字符串本身
真正要用好 Uri 类做 SSRF 防御,必须配三步校验
它提供的是“可信赖的组件提取能力”,不是“安全开关”。你得在拿到 $uri 后,手动执行以下检查:
- 协议白名单:
in_array($uri->getScheme(), ['http', 'https'], true)—— 拒绝gopher、dict、php等所有非标协议 - 主机合法性:
$host = $uri->getHost();后调用gethostbyname($host)或dns_get_record($host, DNS_A + DNS_AAAA)获取真实 IP,再用filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)排除内网段 - 端口限制:
$port = $uri->getPort() ?: ($uri->getScheme() === 'https' ? 443 : 80);,然后显式比对是否在允许列表(如[80, 443, 8080])中
别跳过 DNS 解析这一步,否则等于没防
只靠 parse_url() 提取 host 是严重漏洞点:attacker.com@127.0.0.1、127.0.0.1.xn--example-xxx.com、短链接、CNAME 绕过等都依赖 host 字符串层面的欺骗。Uri 类能帮你干净地拿到 getHost(),但必须立刻做真实 DNS 查询——否则攻击者只需注册一个解析到内网的域名就能绕过所有校验。
注意:gethostbyname() 可能被 DNS 劫持或污染,生产环境建议搭配 dns_get_record() 并校验返回的 A/AAAA 记录是否全部落在白名单范围内。
URI 规范化后比较,才能堵住编码绕过
CVE-2026-44928 修复了 equals() 方法对编码差异的误判,这意味着你现在可以用 $uri1->equals($uri2) 安全判断两个 URL 是否语义等价。这对 SSRF 防御很关键:
- 防止攻击者用
http://example.com/%61dmin(%61=a)绕过路径白名单 - 避免缓存层因
/api?x=1&y=2和/api?y=2&x=1被当成不同键,导致敏感响应被错误缓存 - 但注意:
equals()不代替业务规则——它只确认语法等价,不保证目标合法
最易被忽略的一点:你得先调用 $uri->normalize() 再比较,否则原始输入里的大小写、默认端口、路径冗余(如 /a/../b)仍可能造成校验偏差。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











