错误页面并不是只能放静态HTML。如果服务器错误处理指令指向一个PHP文件,同时该文件位于PHP解析范围内,那么触发404或403时,PHP代码就会执行。这个机制在统一错误处理、记录攻击日志、返回动态内容等场景中很常见,但也可能因为配置错误被滥用。理解它的关键不是编写多复杂的PHP脚本,而是弄清错误页与Web服务器之间的内部转发关系。

错误页面触发PHP执行的前提
Apache和Nginx都提供了错误页面自定义功能。以Apache为例,ErrorDocument指令可以把404、403、500等状态码映射到本地路径或远程URL。当请求的路径不存在时,服务器不会直接返回默认错误页,而是把请求内部交给指定的资源处理。如果这个资源恰好是.php文件,并且当前目录或虚拟主机已经配置了PHP解析,那么该文件就会被PHP解释器执行。
这里有一个容易混淆的地方:浏览器的地址栏不会因为内部重定向而改变。用户访问/nonexistent-page时,地址仍然显示这个不存在的路径,但服务器内部已经转入了/error.php。这种内部子请求会把原始请求信息写入环境变量,例如Apache会生成REDIRECT_URL、REDIRECT_QUERY_STRING等变量,Nginx则通过$request_uri和$uri区分原始请求与当前错误处理路径。PHP脚本可以直接读取这些变量,从而知道用户最初访问的是什么地址。
因此,触发PHP执行并不是因为错误码本身有特殊能力,而是错误处理目标落在了一个可执行PHP的文件上。如果错误页指向的是纯HTML文件,或者目标路径没有被PHP处理器接管,那么即使配置了错误处理指令,也只会输出静态内容或源码,不会执行PHP代码。
Apache环境下的配置方法
Apache可以通过主配置文件、虚拟主机配置或站点目录下的.htaccess文件来设置错误页面。最简单的方式是在.htaccess中写入一条指令,把404错误交给同目录下的PHP脚本处理。例如:
ErrorDocument 404 /error.php
要让.htaccess中的错误处理指令生效,必须保证Apache允许目录级覆盖。主配置中通常需要类似下面的设置:
<Directory "/var/www/html">
AllowOverride All
</Directory>
配置完成后,可以在站点根目录创建error.php,用它记录错误触发时的原始请求信息。下面是一个用于验证的脚本:
<?php
header('Content-Type: text/plain; charset=utf-8');
echo '错误页已触发' . PHP_EOL;
echo '原始请求URI: ' . ($_SERVER['REQUEST_URI'] ?? '无') . PHP_EOL;
echo '重定向前URI: ' . ($_SERVER['REDIRECT_URL'] ?? '无') . PHP_EOL;
echo '查询参数id: ' . ($_GET['id'] ?? '无') . PHP_EOL;
当访问一个不存在的地址并带上查询参数时,例如/nopage?id=7,Apache会把请求内部转给/error.php。脚本输出的REQUEST_URI可能在不同Apache版本中略有差异,但通常能拿到原始请求路径,REDIRECT_URL则会保存发生错误前的URI。通过这种方式,错误页可以精确知道用户误访问了哪个地址。
需要注意的是,如果PHP脚本本身存在漏洞,例如直接拼接用户传入的查询参数到文件包含或命令执行函数中,那么错误页就会成为攻击入口。因此错误处理脚本应当只做展示和记录,避免执行任何来自用户的动态逻辑。
Nginx环境下的配置差异
Nginx没有.htaccess机制,所有错误页配置都集中在server块中。Nginx本身不解析PHP,必须配合fastcgi_pass把请求转给PHP-FPM。因此,要让错误页执行PHP,需要同时满足两个条件:一是用error_page指令指定错误处理路径,二是该路径有对应的location块交给PHP处理。
下面是一个典型的Nginx配置示例:
server {
listen 80;
server_name ipipp.com;
root /var/www/html;
location / {
try_files $uri $uri/ =404;
}
error_page 404 /error.php;
location = /error.php {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root/error.php;
fastcgi_param REQUEST_URI $request_uri;
include fastcgi_params;
}
}
在这个配置中,当请求的静态文件不存在时,try_files会返回404状态。接着error_page 404 /error.php触发内部重定向,请求被转到/error.php这个精确匹配的location。由于该location配置了fastcgi_pass,PHP-FPM会执行error.php文件,同时REQUEST_URI参数被手动设置为原始请求URI,这样脚本就能拿到用户最初访问的地址。
与Apache不同,Nginx的内部重定向会更明显地改变$uri变量。如果不显式传递$request_uri,PHP端的REQUEST_URI可能会变成/error.php,导致脚本无法判断原始请求。因此排查Nginx错误页执行问题时,建议先输出$_SERVER数组,确认哪些变量保留了原始信息。
安全风险与排查思路
错误页执行PHP本身不是漏洞,真正的风险来自两点:一是错误页文件可写或路径可被用户控制,二是.htaccess允许普通用户自定义错误处理目标。假设某个站点允许上传文件到错误页同目录,攻击者就能替换error.php内容,随后请求一个不存在的地址触发执行。类似地,如果用户可以通过配置修改错误处理路径,也能把404指向一个恶意PHP文件。
排查时可以优先检查站点根目录下是否存在ErrorDocument指向.php的.htaccess,再确认该PHP文件是否有异常函数调用,比如eval、assert、system、passthru等。对于Nginx,重点看error_page指令后面的路径是否落在可上传目录,以及对应location是否被PHP处理器接管。
加固方面,建议生产环境设置AllowOverride None,把错误处理逻辑放在主配置文件中统一管理。错误页目录应设为只读,PHP-FPM使用独立用户运行,并限制open_basedir。如果只是想展示友好错误页,优先使用纯HTML或模板静态化,避免错误页本身具备动态执行能力。对于必须使用PHP的错误处理场景,应严格控制脚本内容,只输出固定文案和必要的访问日志,不接收任何用户可控代码。