$_SERVER 中最可靠的5个键是:PHP_SELF(脚本相对路径)、REQUEST_METHOD(真实HTTP方法)、SCRIPT_FILENAME(脚本绝对路径)、REQUEST_TIME(请求时间戳)、SERVER_PROTOCOL(协议版本),它们由服务器可信注入,跨环境稳定。

$_SERVER 不是“万能服务器信息源”,它依赖 Web 服务器(Apache/Nginx)实际注入的字段,不同环境返回的键可能完全不同;直接读取未校验的 $_SERVER['HTTP_REFERER'] 或 $_SERVER['HTTP_HOST'] 容易被伪造,引发安全漏洞。
哪些 $_SERVER 键在绝大多数环境中都可靠?
不是所有键都能放心用。以下 5 个在 Apache、Nginx、CLI(部分)下基本稳定存在,且值来源可信:
-
$_SERVER['PHP_SELF']:当前执行脚本的路径(相对于文档根目录),如/index.php;注意不含查询字符串,也不含域名 -
$_SERVER['REQUEST_METHOD']:真实 HTTP 方法(GET、POST、PUT等),由服务器解析请求行得出,不可伪造 -
$_SERVER['SCRIPT_FILENAME']:脚本在文件系统中的绝对路径,如/var/www/html/index.php;可用于 realpath 校验或 include 安全判断 -
$_SERVER['REQUEST_TIME']:请求开始时的 Unix 时间戳(秒级),精度足够做简单超时控制;PHP 7.1+ 推荐改用$_SERVER['REQUEST_TIME_FLOAT'] -
$_SERVER['SERVER_PROTOCOL']:协议版本(HTTP/1.1或HTTP/2),由服务器设定,不依赖客户端头
HTTP_* 类键为什么不能直接信任?
以 $_SERVER['HTTP_HOST'] 和 $_SERVER['HTTP_REFERER'] 为例:它们本质是客户端发送的 HTTP 请求头字段,Web 服务器原样转交,不做验证。
- 攻击者可手动构造请求,把
Host: evil.com发给你的站点,若代码中直接拼接进跳转链接或日志,就可能造成开放重定向或 SSRF -
$_SERVER['HTTP_REFERER']在隐私模式、HTTPS→HTTP 跳转、或代理拦截时经常为空;更严重的是,它完全可被 curl -H "Referer: ..." 伪造 - 同理,
$_SERVER['HTTP_USER_AGENT']只适合统计或降级兼容,绝不能用于权限判断(比如“只允许 Chrome 访问”)
SERVER_NAME 和 HTTP_HOST 的区别在哪?
这两个常被混用,但行为逻辑完全不同:
立即学习“PHP免费学习笔记(深入)”;
-
$_SERVER['SERVER_NAME']来自 Web 服务器配置(如 Apache 的ServerName指令),是静态定义的主机名,不随请求变化;即使用户访问http://192.168.1.100,它仍返回配置值(如example.com) -
$_SERVER['HTTP_HOST']来自请求头Host:字段,是客户端声明的“我想访问哪个域名”,可能为 IP、带端口(localhost:8080)、甚至非法字符串(foo%00bar) - 判断当前站点主域名时,优先用
$_SERVER['SERVER_NAME'];需要支持多租户子域名(如 user1.example.com)时,才解析$_SERVER['HTTP_HOST'],且必须白名单过滤
$_SERVER 中容易踩坑的冷门键
有些键看似有用,但实际场景中极易出错:
-
$_SERVER['PATH_INFO']:仅当服务器启用 PATH_INFO 支持(如 Apache 的AcceptPathInfo On)且 URL 形如/index.php/user/123时才非空;Nginx 默认不传递,需显式配置fastcgi_split_path_info -
$_SERVER['QUERY_STRING']是原始未解码字符串(如a=b%20c),不能直接用于 SQL 或 HTML 输出;应先urldecode()再处理 -
$_SERVER['REMOTE_ADDR']在反向代理(Nginx → PHP-FPM)后通常变成代理 IP;要获取真实客户端 IP,需检查$_SERVER['HTTP_X_FORWARDED_FOR'],但该字段同样可伪造,必须结合$_SERVER['REMOTE_ADDR']是否在可信代理列表中做双重验证
最常被忽略的一点:$_SERVER 数组本身不是只读的——你可以在脚本中修改它的值(比如 $_SERVER['HTTP_HOST'] = 'safe.example.com';),但这只影响当前请求生命周期内的 PHP 层,不会改变服务器行为,也无助于防御,反而可能掩盖问题。



















