本文目录导读:

在 PHP 中防御 SSRF(Server-Side Request Forgery,服务端请求伪造)攻击,核心思路是 严格限制服务端发起的网络请求,防止攻击者利用漏洞访问内部网络或恶意外部资源。
以下是 PHP 防御 SSRF 的 7 个核心策略,按推荐程度排序:
白名单机制(最有效)
只允许访问预定义的、受信任的主机或 IP 地址。
<?php
function safeCurl($url) {
$whitelist = [
'api.trusted-service.com',
'www.example.com',
'10.0.0.1' // 仅限特定内部服务
];
$host = parse_url($url, PHP_URL_HOST);
if (!in_array($host, $whitelist)) {
throw new Exception("Host not allowed");
}
// 执行请求...
}
IP 地址验证与过滤
过滤私有 IP 地址(如 10.x.x.x、172.16-31.x.x、192.168.x.x)和保留地址(127.0.0.1、0.0.0.0)。
<?php
function isPrivateIp($ip) {
$privateRanges = [
'10.0.0.0' => '8',
'172.16.0.0' => '12',
'192.168.0.0' => '16',
'127.0.0.0' => '8', // loopback
'169.254.0.0' => '16', // link-local
'0.0.0.0' => '8' // 通常表示所有接口
];
foreach ($privateRanges as $range => $mask) {
if (ipInRange($ip, $range, $mask)) {
return true;
}
}
return false;
}
function safeRequest($url) {
$host = parse_url($url, PHP_URL_HOST);
// 先解析为 IP(防止 DNS 欺骗,多次解析确认)
$ip = gethostbyname($host);
if (isPrivateIp($ip)) {
throw new Exception("Access to internal IP denied");
}
// 额外检查 IPv6 的私有地址(fd00::/8 等)
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$ipv6 = inet_pton($ip);
if (strlen($ipv6) >= 8 && ($ipv6[0] & 0xfe) === 0xfc) {
throw new Exception("Private IPv6 denied");
}
}
// 执行 cURL 请求...
}
禁用重定向跟随
攻击者可能利用 HTTP 重定向将请求引导到内部资源。
<?php $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // 关闭自动跟随重定向 // 或者手动处理重定向并验证每个跳转的目标
更好的做法是拦截重定向并重新验证:
<?php curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_MAXREDIRS, 5); // 限制重定向次数 // 或者使用 CURLOPT_PROTOCOLS 限制协议
使用 parse_url 二次验证
攻击者可能利用 URL 解析差异绕过验证(如 http://evil.com\@internal 或 http://evil.com#@internal)。
<?php
function validateUrl($url) {
$parsed = parse_url($url);
// 检查是否包含用户信息(@ 符号前面部分)
if (isset($parsed['user']) || isset($parsed['pass'])) {
throw new Exception("URL with credentials not allowed");
}
// 检查 scheme
$allowedSchemes = ['http', 'https'];
if (!in_array($parsed['scheme'], $allowedSchemes)) {
throw new Exception("Scheme not allowed");
}
// 检查路径是否包含 ../ 等路径穿越
if (preg_match('/\.\.\//', $parsed['path'])) {
throw new Exception("Path traversal detected");
}
return true;
}
对 cURL 进行精细化配置
<?php
function secureCurlRequest($url) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_FOLLOWLOCATION => false, // 手动处理重定向
CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS, // 限制协议
CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
CURLOPT_MAXREDIRS => 0, // 禁止重定向
CURLOPT_SSL_VERIFYPEER => true, // 验证 SSL 证书
CURLOPT_SSL_VERIFYHOST => 2,
// 禁止使用本地文件协议(避免 file:// 攻击)
CURLOPT_FILE => null,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$finalUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
// 检查最终请求的 URL 是否合法
if ($finalUrl !== $url) {
throw new Exception("URL changed during request");
}
curl_close($ch);
return $response;
}
网络层限制(最底层防御)
在服务器防火墙层面限制出站流量,只允许必要的出站 IP/端口。
# iptables 禁止访问私有 IP 地址 iptables -A OUTPUT -d 10.0.0.0/8 -j DROP iptables -A OUTPUT -d 172.16.0.0/12 -j DROP iptables -A OUTPUT -d 192.168.0.0/16 -j DROP iptables -A OUTPUT -d 127.0.0.0/8 -j DROP iptables -A OUTPUT -d 169.254.0.0/16 -j DROP # 只允许访问特定外部服务 iptables -A OUTPUT -d api.example.com -j ACCEPT iptables -A OUTPUT -j DROP # 默认拒绝所有其他出站
防御 DNS 重绑定攻击
攻击者可能使用域名在第一次解析时返回正常 IP,第二次返回内部 IP,使用 多次 DNS 解析并比较 IP:
<?php
function dnsRebindingCheck($host) {
$ips1 = dns_get_record($host, DNS_A);
$ips2 = dns_get_record($host, DNS_A);
if ($ips1 !== $ips2) {
throw new Exception("DNS rebinding attack detected");
}
// 或者使用 gethostbyname 获取一个 IP,再使用 dns_get_record 获取多个
$ip1 = gethostbyname($host);
$records = dns_get_record($host, DNS_A);
$ip2 = $records[0]['ip'] ?? '';
if ($ip1 !== $ip2) {
throw new Exception("Inconsistent DNS resolution");
}
}
综合防御示例
<?php
class SsrfGuard {
private $whitelist = [];
private $blockPrivate = true;
public function validate($url) {
$parsed = parse_url($url);
// 1. 检查 scheme
if (!in_array($parsed['scheme'], ['http', 'https'])) {
throw new \Exception("Invalid scheme");
}
// 2. 检查白名单
if (!empty($this->whitelist)) {
if (!in_array($parsed['host'], $this->whitelist)) {
throw new \Exception("Host not in whitelist");
}
}
// 3. 阻止私有 IP
if ($this->blockPrivate) {
$ip = gethostbyname($parsed['host']);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
throw new \Exception("Private IP not allowed");
}
}
// 4. 禁止路径穿越
if (preg_match('/\.\.\//', $parsed['path'] ?? '')) {
throw new \Exception("Path traversal detected");
}
return true;
}
public function fetch($url) {
$this->validate($url);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 5,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
CURLOPT_SSL_VERIFYPEER => true,
]);
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
}
总结防御要点
- 永远不要信任用户输入的 URL,即使是看起来正常的域名。
- 优先使用白名单,如果无法使用白名单,至少使用黑名单过滤私有 IP。
- 禁用 cURL 的重定向 或者手动验证每个跳转。
- 限制协议 只允许 http/https,禁止 file/ftp/gopher 等协议。
- 在系统和网络层面 配置防火墙规则限制出站流量。
- SSRF 漏洞常见于:图片抓取、网页截图、URL 预览、API 代理、文件下载等功能。