http头注入本质是利用\r\n换行截断伪造响应头,php的header()函数不自动过滤控制字符,故所有拼接用户输入的响应头(如location、set-cookie)必须经白名单校验与上下文感知清洗,禁用原始字节注入。

HTTP头注入的本质是换行截断
PHP里用header()函数写响应头时,如果参数里混入\r或\n,就会被浏览器解析成两个独立的HTTP头——攻击者借此插入Set-Cookie、Location甚至伪造整个响应体。这不是PHP特有漏洞,而是所有语言共通的协议层风险。
关键点在于:PHP不会自动过滤这些控制字符,header()函数照单全收。所以任何拼接进header()的变量,只要可能含用户输入(比如从$_GET取值生成跳转地址),就必须先清理。
- 常见错误场景:
header('Location: ' . $_GET['url']),攻击者传url=https://a.com%0D%0ASet-Cookie:admin=1即可劫持后续请求 - 危险字符不止
%0D%0A,原始字节\r\n、\n、\r都要拦截 - 不能只依赖
urlencode()或rawurlencode(),它们只编码URL,不处理换行符本身
用filter_var()做白名单校验比正则更可靠
对要写入响应头的字符串,优先走白名单校验而非黑名单过滤。比如跳转地址,就该限定为合法URL结构;自定义X-User-ID头,就该只允许数字和短横线。
filter_var()内置的FILTER_VALIDATE_URL和FILTER_SANITIZE_URL能覆盖大部分场景,但要注意:
-
FILTER_SANITIZE_URL会删掉\r\n,但也会删掉合法的@、:等字符,不适合用于完整URL拼接 - 更稳妥的是组合使用:
filter_var($input, FILTER_VALIDATE_URL)先校验,再用str_replace(["\r", "\n"], '', $input)做兜底 - 若字段只需简单格式(如用户名、ID),直接用
filter_var($input, FILTER_SANITIZE_STRING)并配合ctype_alnum()判断更轻量
响应头写入前必须做上下文感知处理
不同响应头对内容要求差异极大:Content-Type要严格匹配MIME类型,Set-Cookie有固定语法,Location必须是绝对URI。不能用同一套逻辑处理所有头。
实操建议:
-
Location头:用filter_var($url, FILTER_VALIDATE_URL)+parse_url()检查scheme是否为https或http,拒绝javascript:、data:等伪协议 -
Set-Cookie头:不要手动拼接,改用setcookie()函数,它内部已做安全处理 - 自定义头(如
X-Trace-ID):用preg_replace('/[^a-zA-Z0-9\-_\.]/', '', $value)白名单清洗,长度限制在64字符内 - 永远避免把
$_SERVER['HTTP_*']变量原样塞进header(),比如$_SERVER['HTTP_REFERER']可能含恶意换行
Apache/Nginx配置可作为最后一道防线
即使PHP代码漏判,Web服务器层也能拦截明显异常的响应头。但这只是纵深防御的补充,不能替代代码层校验。
Apache示例(.htaccess或虚拟主机配置):
Header always set X-Content-Type-Options "nosniff" Header always set X-Frame-Options "DENY" # 拒绝包含\r\n的Set-Cookie头(需mod_headers) Header edit Set-Cookie ^(.*)$ "$1; HttpOnly; Secure"
Nginx中可用add_header强制覆盖,但无法动态过滤响应头内容。真正有效的服务器级防护是禁用危险模块(如Apache的mod_userdir)和关闭AllowOverride All,防止.htaccess被恶意覆盖。
最易被忽略的一点:开发环境常关掉display_errors,但忘了关expose_php。暴露PHP版本号本身虽不直接导致头注入,却为攻击者选择利用链提供线索——这类细节往往在上线前被跳过。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











