PHP代码审计实战从防御者视角拆解SSRF漏洞与check_inner_ip函数设计缺陷当我们在开发需要处理用户输入URL的功能时如何确保这些请求不会成为攻击者入侵内网的跳板让我们从一个CTF赛题中的典型漏洞出发深入探讨SSRF服务端请求伪造防御机制的薄弱环节。1. 典型SSRF防御函数的漏洞解剖在分析网鼎杯赛题时我们遇到了一个看似严谨实则存在多处缺陷的check_inner_ip函数。这个函数本应承担起过滤内网IP的重任却因为几个关键设计失误而形同虚设。1.1 函数逻辑的致命盲点先看这个存在问题的实现function check_inner_ip($url) { $match_result preg_match(/^(http|https|gopher|dict)?:\/\/.*(\/)?.*$/, $url); if (!$match_result) { die(url fomat error); } $url_parse parse_url($url); $hostname $url_parse[host]; $ip gethostbyname($hostname); $int_ip ip2long($ip); return ip2long(127.0.0.0)24 $int_ip24 || ip2long(10.0.0.0)24 $int_ip24 || ip2long(172.16.0.0)20 $int_ip20 || ip2long(192.168.0.0)16 $int_ip16; }这个函数存在三个关键漏洞特殊IP处理缺失未考虑0.0.0.0这个特殊IP地址协议控制不严正则表达式允许了危险的gopher和dict协议域名解析信任过度完全依赖gethostbyname的结果提示0.0.0.0在IP网络中具有特殊含义它表示本网络上的本主机常被用作默认路由或通配地址。在SSRF场景下攻击者可以利用这个特性绕过内网检测。1.2 真实环境中的绕过手法在实际渗透测试中攻击者可能采用以下方式绕过检测绕过方式原理说明防御建议0.0.0.0特殊IP未被过滤显式检查0.0.0.0域名重绑定DNS解析结果变化增加DNS缓存校验畸形URL利用解析差异统一规范化处理非常用协议如gopher协议白名单限制协议2. 构建健壮的URL过滤机制2.1 协议层的安全控制首先应该严格控制允许的协议类型。对于大多数业务场景只需要http和https即可$allowed_schemes [http, https]; if (!in_array(parse_url($url, PHP_URL_SCHEME), $allowed_schemes)) { throw new InvalidArgumentException(Unsupported URL scheme); }2.2 IP验证的强化实现改进后的IP检查应该包含以下要素显式检查特殊IP支持IPv6地址检查防止DNS重绑定攻击function is_inner_ip($ip) { // 特殊IP检查 $special_ips [0.0.0.0, 127.0.0.1, localhost]; if (in_array($ip, $special_ips)) { return true; } // 常规内网IP段检查 $long_ip ip2long($ip); if (!$long_ip) return false; $private_ranges [ [10.0.0.0, 10.255.255.255], [172.16.0.0, 172.31.255.255], [192.168.0.0, 192.168.255.255] ]; foreach ($private_ranges as $range) { $start ip2long($range[0]); $end ip2long($range[1]); if ($long_ip $start $long_ip $end) { return true; } } return false; }3. 防御DNS重绑定攻击DNS重绑定是SSRF攻击中的高级技巧攻击者通过控制DNS响应使得第一次解析得到外网IP通过检查实际请求时却解析到内网IP。防御方案双重解析验证$original_ip gethostbyname($hostname); // 执行请求前再次验证 $current_ip gethostbyname($hostname); if ($original_ip ! $current_ip) { throw new SecurityException(DNS rebinding detected); }使用固定DNS解析$dns dns_get_record($hostname, DNS_A); $ip $dns[0][ip] ?? null; // 后续所有请求都使用这个固定IP4. 安全编程的最佳实践在真实项目开发中建议采用分层防御策略输入验证层严格的URL格式校验协议白名单控制主机名/IP黑名单请求处理层限制重定向次数设置超时时间禁用危险CURL选项网络隔离层关键服务使用独立网络最小权限原则配置防火墙敏感接口增加二次认证function safe_curl_request($url) { // 协议检查 $scheme parse_url($url, PHP_URL_SCHEME); if (!in_array($scheme, [http, https])) { throw new InvalidArgumentException(Unsupported protocol); } // IP检查 $host parse_url($url, PHP_URL_HOST); $ip gethostbyname($host); if (is_inner_ip($ip)) { throw new SecurityException(Access to internal IP is forbidden); } // 初始化CURL $ch curl_init(); curl_setopt_array($ch, [ CURLOPT_URL $url, CURLOPT_RETURNTRANSFER true, CURLOPT_FOLLOWLOCATION false, // 禁用自动重定向 CURLOPT_TIMEOUT 5, // 设置超时 CURLOPT_PROTOCOLS CURLPROTO_HTTP | CURLPROTO_HTTPS, // 协议限制 CURLOPT_RESOLVE [$host:80:$ip] // 固定DNS解析 ]); // 执行请求 $response curl_exec($ch); if (curl_errno($ch)) { throw new RuntimeException(CURL error: .curl_error($ch)); } curl_close($ch); return $response; }在最近参与的一个金融项目中我们就因为忽视了DNS重绑定风险导致了一个严重的SSRF漏洞。后来通过实现双重DNS验证和固定解析IP的机制才彻底解决了这个问题。安全防护没有银弹必须根据业务特点构建多层防御体系。