SSRF漏洞本质是服务端未校验用户输入的URL,直接调用file_get_contents()、curl_init()等函数发起请求;这些函数默认信任所有协议和地址,不解析域名、不查IP段、不限制端口,导致攻击者可构造http://127.0.0.1、gopher://、dict://等恶意URL访问内网或读取本地文件,必须通过协议白名单、IP段校验、禁用重定向、防DNS重绑定等三层校验才能有效防御。

PHP 8.3.36 本身不修复 SSRF,修复靠你写的代码逻辑——不是升级 PHP 版本就能关掉这个漏洞。
为什么 file_get_contents()、curl_init() 这类函数直接拼用户输入就是高危
它们不校验协议、不解析域名、不查 IP 段,只管发请求。用户传 http://127.0.0.1:6379/ 或 gopher://192.168.1.100:6379/,PHP 就真去连。关键在于:这些函数默认信任所有 URL 字符串,而 SSRF 的入口往往就藏在“看起来只是个链接”的参数里。
常见错误现象:
- 接口返回 500 但没报错,实际已向内网发包(盲 SSRF)
- 加了
parse_url()判断scheme === 'https',但被http://attacker.com@127.0.0.1绕过 - 用
gethostbyname()查域名 IP,但没处理 DNS 重绑定(Rebinding)
必须做三层校验:协议 → 域名/IP → 端口
只拦 file:// 或只白名单 https:// 不够,攻击者会用 dict://、gopher://、甚至 http://evil.com@192.168.0.1 跳过前端过滤。
立即学习“PHP免费学习笔记(深入)”;
实操建议:
修复 Core bailout 状态损坏安全问题,以及 BZ2、DOM、PDO、Opcache/JIT 内存与执行错误,改进 Unicode 字素处理、URL 解析、TLS 流状态、信号分发及会话错误处理。
- 强制限制
scheme为https和http,显式拒绝file、gopher、dict、ftp、telnet等非必要协议 - 对
host部分做标准化解析:先parse_url(),再getaddrinfo()或dns_get_record()获取真实 IP,避免被@、%%、IPv6 写法绕过 - 检查解析出的 IP 是否落在私有地址段:
127.0.0.0/8、10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、100.64.0.0/10(运营商级 NAT) - 限制端口:只允许
80、443、8080、8443等常见 Web 端口,明确拒绝6379、3306、22、11211
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false) 必须设为 false
重定向是 SSRF 绕过的高频手段。比如你校验了原始 URL 是公网域名,但服务端 302 跳转到 http://192.168.5.5/metadata,后续请求就进了内网。PHP 的 cURL 默认开启重定向,且不会重新校验跳转后的地址。
正确做法:
- 显式设置
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false) - 如果业务必须支持重定向,需手动实现逐跳校验:每次收到
Location头,都重新走一遍协议/域名/IP/端口校验流程 - 注意
curl_setopt($ch, CURLOPT_UNRESTRICTED_AUTH, true)会把认证头带入重定向,加剧风险,应禁用
别信 DNS 缓存,gethostbyname() 不防 Rebinding
DNS 重绑定攻击能让同一个域名在一次请求中解析出不同 IP(比如第一次返回 1.1.1.1 过校验,第二次返回 192.168.1.100)。很多开发者以为查一次 DNS 就安全了,其实只要服务端没做连接池隔离或缓存锁定,就可能中招。
更稳妥的做法:
- 用
dns_get_record($host, DNS_A + DNS_AAAA)获取全部记录,再对每个结果 IP 做私有段校验 - 若业务允许,直接禁用域名解析,只接受 IP 地址(并校验格式和段)
- 在企业级场景,建议把所有出站请求统一走代理网关,由网关层做出口 IP 白名单和协议过滤,PHP 层只对接可信内部地址
真正容易被忽略的是:SSRF 防御不是“加个 if 判断”,而是整个请求链路的控制权移交——从“用户说去哪就去哪”,变成“只准去我列好的白名单里那几个地方”。哪怕只漏一个协议、一个端口、一次重定向,整条防线就形同虚设。


















