PHP的远程文件包含漏洞,核心在于allow_url_include这个配置项。很多运维人员只记得关掉allow_url_fopen,却忽略了allow_url_include才是直接控制能否将远程文件当作PHP代码执行的关键开关。从PHP 5.2开始,allow_url_include默认就是Off,但大量老旧系统、手动编译的环境或者被二次修改的配置文件里,它依然可能是开启状态。更隐蔽的是,即使allow_url_fopen关闭了,只要allow_url_include开启,攻击者依然可以通过其他方式引入远程内容,比如利用PHP的某些流封装器。所以,禁用远程文件包含,本质上是要从PHP配置、代码编写、服务器架构三个层面同时下手,单靠一个配置项远远不够。
php.ini中的核心配置项首先要明确两个配置的区别。allow_url_fopen控制的是PHP文件函数能否打开远程文件,比如fopen、file_get_contents能否读取http或ftp协议的URL。allow_url_include控制的是include、require等文件包含函数能否直接包含远程文件。很多人误以为关闭allow_url_fopen就万事大吉,实际上,如果allow_url_include是开启状态,攻击者仍然可以利用某些特殊协议或者结合其他漏洞实现代码执行。正确的做法是:
打开php.ini文件,找到以下两行,确保它们都被设置为Off:
allow_url_fopen = Off allow_url_include = Off
修改后必须重启PHP-FPM或者Web服务器才能生效。可以用phpinfo()函数验证配置是否生效,在页面中搜索这两个配置项,确认Local Value和Master Value都显示为Off。需要注意的是,有些虚拟主机面板或者云服务商可能提供了单独的PHP配置覆盖功能,比如.user.ini文件或者面板中的PHP设置页面,这些地方也需要检查。
disable_functions和disable_classes的配合使用即使关闭了远程文件包含的配置,攻击者还可能通过其他函数来执行系统命令或者读取敏感文件,为后续的本地文件包含攻击铺路。因此,在php.ini中合理配置disable_functions是纵深防御的重要一环。建议至少禁用以下高危函数:
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
这个列表可以根据实际业务需求增减。比如,如果应用不需要发送网络请求,可以把curl_exec和curl_multi_exec也禁掉。但要注意,有些正常的PHP框架或库会用到这些函数,禁用前务必在测试环境验证。disable_classes则可以禁用特定的PHP类,不过实际使用场景较少,一般不需要配置。
open_basedir限制文件访问范围open_basedir是PHP中一个经常被低估的安全配置。它限定了PHP进程能访问的文件目录范围,即使攻击者成功利用了某个文件包含漏洞,也会被限制在指定的目录内,无法读取系统关键文件或者跨越到其他网站目录。配置方式是在php.ini中设置:
open_basedir = /var/www/html:/tmp
多个目录用冒号分隔,Linux系统下路径区分大小写。这个配置会影响文件读写、include等操作。设置后,PHP尝试访问限制目录之外的文件时会直接报错。需要注意的是,open_basedir对性能有轻微影响,而且如果设置不当会导致正常业务无法运行,比如上传文件的临时目录、会话存储目录、日志目录等都需要包含在内。建议先在测试环境充分验证,再部署到生产环境。
Web服务器层面的安全加固PHP配置只是第一道防线,Web服务器本身也需要配合加固。以Nginx为例,可以通过location指令来阻止对特定文件类型的直接访问,或者限制请求中的危险参数。例如,在nginx.conf中添加:
location ~* \.(php|php5)$ {
# 常规的PHP解析配置
}
# 阻止直接访问包含敏感关键词的URL
if ($query_string ~* "(\<|%3C).*script.*(\>|%3E)"){
return 403;
}
if ($query_string ~* "GLOBALS(=|\[|\%[0-9A-Z]{0,2})"){
return 403;
}
if ($query_string ~* "_REQUEST(=|\[|\%[0-9A-Z]{0,2})"){
return 403;
}
Apache用户可以通过mod_rewrite或者mod_security模块来实现类似规则。更推荐的做法是部署WAF(Web应用防火墙),无论是开源的ModSecurity还是云服务商提供的WAF产品,都能有效拦截常见的文件包含攻击payload。WAF规则需要定期更新,以应对新的绕过手法。
代码层面的防御措施配置再完善,如果代码本身存在漏洞,防御效果也会大打折扣。PHP文件包含漏洞最常见的成因是动态包含文件时,用户输入的参数未经严格过滤直接拼接到include或require语句中。例如:
$file = $_GET['page']; include($file . '.php');
这种写法极其危险。即使allow_url_include关闭了,攻击者依然可以通过路径遍历读取服务器上的任意文件,比如使用../../etc/passwd这样的payload,或者结合文件上传漏洞包含恶意文件。正确的做法是使用白名单机制:
$allowed_pages = ['home', 'about', 'contact', 'products'];
$page = $_GET['page'] ?? 'home';
if (in_array($page, $allowed_pages)) {
include($page . '.php');
} else {
// 记录日志并返回404
error_log("Invalid page request: " . $page);
header("HTTP/1.0 404 Not Found");
exit;
}
白名单是最安全的方式,因为攻击者无论如何修改参数值,只要不在白名单内就无法通过验证。如果业务场景确实需要动态包含不同目录下的文件,也要对输入做严格过滤,比如只允许字母、数字和下划线,并且限制路径中不能出现../这样的目录遍历字符。
PHP版本和补丁管理PHP官方在过去十几年中修复了大量与文件包含相关的安全漏洞。例如,某些PHP版本中存在协议封装器的绕过漏洞,攻击者可以利用data://、php://input等协议在allow_url_include关闭的情况下仍然实现代码执行。因此,保持PHP版本在官方支持的稳定版本范围内至关重要。截止到2025年,PHP 8.1及以上版本是推荐的选择,PHP 7.4及更早版本已经停止安全更新,不应继续在生产环境使用。除了主版本升级,也要关注操作系统层面的PHP包更新,及时安装安全补丁。
日志监控和入侵检测防御措施部署完毕后,还需要建立有效的监控机制。文件包含攻击通常会在Web服务器访问日志中留下明显的痕迹,比如URL参数中出现http://、ftp://、php://等协议标识,或者出现大量的../路径遍历字符。可以编写简单的脚本定期分析日志,或者使用ELK、Splunk等日志分析平台设置告警规则。例如,用grep快速检查Nginx日志中的可疑请求:
grep -E "(http://|ftp://|php://|data://|expect://|\.\./)" /var/log/nginx/access.log
此外,文件完整性监控工具如AIDE、Tripwire可以检测到Web目录下新增的可疑文件,这些文件可能是攻击者通过文件包含漏洞写入的webshell。一旦发现异常文件变更,应立即启动应急响应流程。
容器化和隔离环境下的特殊考虑在Docker等容器化部署环境中,PHP运行在独立的容器内,即使攻击者成功利用了文件包含漏洞,影响范围也被限制在容器内部。但这并不意味着可以忽视安全配置。容器内的PHP配置依然需要按照上述原则进行加固,同时要避免将宿主机敏感目录挂载到容器内。容器的文件系统应该以只读方式挂载Web目录,防止攻击者通过文件包含漏洞写入恶意文件。如果业务需要写入文件,应该将写入目录单独挂载,并设置noexec标志,禁止该目录下的文件被执行。
php://input等伪协议的风险处理即使allow_url_include已经关闭,PHP的某些内置流封装器仍然可能被利用。php://input可以读取HTTP请求的原始数据,如果代码中存在如下写法:
include("php://input");
攻击者就可以在POST请求体中直接发送PHP代码并被执行。这种漏洞与allow_url_include配置无关,因为php://是本地的流封装器。防御方法除了代码审查避免这种写法外,还可以在php.ini中设置:
allow_url_fopen = Off
这虽然不能直接禁用php://input,但结合disable_functions和严格的代码规范,可以大幅降低风险。更彻底的做法是在Web服务器层面限制请求体的大小和内容类型,对于不需要接收POST数据的静态资源路径,可以直接拒绝POST请求。
实战中的常见绕过手法与应对攻击者会不断尝试绕过安全配置。常见的绕过手法包括:使用URL编码、双重编码来绕过WAF规则;利用%00截断在PHP 5.3以下的版本中绕过文件扩展名限制;利用路径长度截断在某些系统上绕过白名单检查;使用问号或井号让include函数忽略后面的拼接部分。针对这些手法,防御策略需要多管齐下。在代码层面,除了白名单验证外,还要对输入进行规范化处理,比如先进行URL解码,再去除空字节和路径遍历字符。在WAF层面,需要启用深度解码功能,对请求参数进行递归解码后再匹配规则。在PHP配置层面,确保使用PHP 5.3.4以上版本,这些版本已经修复了%00截断漏洞。
安全配置检查清单为了方便运维人员逐项排查,这里整理一份完整的检查清单。第一,确认allow_url_include和allow_url_fopen均为Off。第二,确认disable_functions包含了exec、system、passthru等命令执行函数。第三,确认open_basedir已正确设置并覆盖了所有需要访问的目录。第四,确认PHP版本在官方安全支持周期内。第五,确认Web服务器已配置基本的恶意请求拦截规则或部署了WAF。第六,确认代码中不存在动态变量直接拼接到include语句的情况。第七,确认日志监控系统正常运行,能够及时发现攻击行为。第八,确认容器或服务器文件系统权限最小化,Web目录不可写或设置了noexec。按照这个清单逐项检查,可以最大程度降低远程文件包含漏洞带来的风险。
