PHP不能直接请求AI返回的URL,因存在SSRF风险;须按协议检查→主机白名单→DNS解析→IP归属四层过滤,禁用自动跳转并递归解码URL。

PHP接收AI返回URL时,为什么不能直接用 file_get_contents() 或 cURL 请求?
因为AI(尤其是第三方大模型API)返回的URL可能是伪造的、重定向到内网地址、或指向恶意资源。PHP默认不校验URL协议、主机合法性、IP范围,直接请求可能触发SSRF(服务器端请求伪造),导致内网探测、本地文件读取甚至RCE。
关键不是“能不能发请求”,而是“有没有先确认这个URL是安全可访问的”。常见错误现象包括:file_get_contents("http://127.0.0.1:8080/admin") 成功执行、cURL 被用于请求 file:///etc/passwd、或跳转到 http://attacker.com/steal?data=xxx。
如何用PHP白名单+协议+DNS+IP四层过滤校验URL?
必须组合验证,单靠 parse_url() 或正则远远不够。推荐顺序:协议检查 → 主机白名单匹配 → DNS解析 → IP归属判断。
-
协议只允许
https和http(禁用ftp、php、file、data、gopher等) -
主机名必须命中预设白名单,例如
["cdn.example.com", "images.unsplash.com", "i.imgur.com"];支持通配符*.unsplash.com,但需用fnmatch()严格比对,不信任str_ends_with() -
必须调用
gethostbyname()或dns_get_record()获取真实IP,防止利用 hosts 绑定绕过域名检查 -
解析出的IP必须排除私有地址段:
127.0.0.0/8、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、::1、fd00::/8等,用filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)
实际代码中如何避免 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true) 引发的跳转绕过?
开启自动跳转会让校验只作用于原始URL,后续302跳转的目标地址完全失控。这是最常被忽略的安全缺口。
立即学习“PHP免费学习笔记(深入)”;
- 始终设置
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false) - 手动处理跳转:检查响应头
Location,对每个新URL重新走一遍上述四层校验 - 限制跳转次数(如最多3次),防止循环重定向耗尽资源
- 若使用
file_get_contents(),不要依赖stream_context_create()中的follow_location,它无法拦截中间跳转URL做校验
$opts = [
'http' => [
'method' => 'GET',
'max_redirects' => 0, // 关键:禁用自动跳转
'timeout' => 5,
]
];
$context = stream_context_create($opts);
AI返回URL含参数时,为什么不能简单用 urldecode() 后再校验?
攻击者可能构造双编码URL(如 http%253A%252F%252F127.0.0.1),一次 urldecode() 变成 http%2F%2F127.0.0.1,再 decode 才暴露真实协议。PHP不会自动解码多次,校验逻辑若只解一次,就会漏掉嵌套编码。
- 校验前必须递归解码直到无变化(但注意限制深度,防DoS)
- 更稳妥做法:在白名单匹配和IP判断前,先用
rawurldecode()解一层,再用parse_url()提取scheme和host,禁止从原始字符串中直接提取 - 特别注意:
parse_url()对畸形URL(如http://evil.com@127.0.0.1)可能误判host为evil.com,实际请求的是127.0.0.1—— 必须用gethostbyname()实际解析结果为准
真正难的不是写校验函数,而是所有环节都得串起来:解码→解析→白名单→DNS→IP段→跳转重校验。少一环,AI返回的URL就可能变成一把捅向你服务器的刀。



















