header() 因换行符(\r、\n及其 url 编码)导致响应头拆分,引发重定向劫持等攻击;必须手动过滤换行符,不可依赖框架默认行为或模板转义。

header() 里塞用户输入,就等于给攻击者开了个 HTTP 响应头的后门。ThinkPHP 不拦截换行符,也不自动过滤 %0d、%0a,这事必须你亲手做。
为什么 header() 会因换行符出问题
PHP 的 header() 函数遇到 \r 或 \n(或其 URL 编码 %0d/%0a)时,会把响应拆成两段——第一段是正常头,第二段被当作新响应体,攻击者就能伪造重定向、注入 Cookie 甚至返回恶意 HTML。这不是 ThinkPHP 漏洞,是 PHP 底层行为,框架没法覆盖。
哪些地方最容易栽在 header() 上
- 重定向时直接拼接
input('get.redirect'):header('Location: ' . input('get.redirect')) - 用
$_SERVER['HTTP_X_FORWARDED_FOR']设置X-Real-IP头 - 调用
response()->header()传入未清洗的变量(ThinkPHP 6+ 中该方法和原生header()行为一致) - 在中间件或钩子中动态写入
Set-Cookie、Content-Security-Policy等头,值来自请求参数
怎么安全地构造响应头
核心就一条:进 header() 的字符串,必须是可信源或经过换行净化后的结果。
- 所有要写入头的变量,提前做换行清理:
$safe_value = str_replace(["\r", "\n", "%0d", "%0a"], '', $unsafe_value)(注意:必须在header()调用前执行,不能只在模板里转义) - 重定向目标优先走内部映射表,比如:
$map = ['profile' => '/user/profile', 'order' => '/order/list']; $url = $map[input('get.to')] ?? '/'; redirect($url); - 若必须跳外部 URL,先
filter_var($url, FILTER_VALIDATE_URL),再parse_url($url, PHP_URL_SCHEME)检查协议是否为https,最后比对parse_url($url, PHP_URL_HOST)是否在域名白名单内 - 不要依赖
input('xxx/s')或htmlspecialchars()——它们对换行符和 URL 编码无效
容易被忽略的“假防护”陷阱
很多开发者以为自己已经防住了,其实只是错觉:
-
input()只是取值代理,不默认过滤换行;/s后缀只强制字符串类型,不是安全过滤 - 模板引擎的
{$var}自动转义只作用于 HTML body,对header()完全没影响 -
response()->header()和header()一样裸奔,ThinkPHP 6+ 不额外做任何换行检查 - 数据库预处理、JSON 解析、验证器这些机制,跟响应头安全毫无关系
真正起作用的,永远是你在调用 header() 前那行 str_replace,以及你有没有把用户输入当成了“可信数据”。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











