thinkphp 5.1+ 中 header() 和 response()->header() 存在 http 响应拆分风险,因默认不过滤 \r\n 等控制字符,用户输入直拼会导致 xss、缓存污染等;response()->redirect() 安全但仅限重定向且不覆盖手动 header();自定义响应头须手动过滤控制字符,推荐封装 safe_header() 或中间件统一拦截。

ThinkPHP 5.1+ 中 header() 和 response()->header() 的风险点
ThinkPHP 默认不自动过滤响应头中的换行符(\r、\n),一旦你把用户输入直接拼进 header() 或 response()->header(),就可能触发 HTTP 响应拆分(HTTP Response Splitting)。典型表现是:返回两个响应,第二个响应被浏览器当作新请求处理,导致 XSS、缓存污染甚至密码窃取。
常见高危写法:
response()->header('Location', $_GET['url']);header('Set-Cookie: user=' . $_POST['name']);$this->assign('redirect', input('next')); // 然后在模板里用 header("Location: {$redirect}")
ThinkPHP 自带的 response()->redirect() 是安全的,但仅限重定向场景
ThinkPHP 的 response()->redirect() 内部会对 URL 做 urlencode() + 协议白名单校验(只允许 http://、https://、/ 开头),并过滤掉换行和控制字符。所以只要用它,不用自己拼 Location 头。
但注意三点:
- 它只处理重定向,不适用于其他响应头(如
X-Forwarded-For、Content-Disposition) - 若传入的是相对路径(如
redirect('/user?name=xxx')),不会做 URL 编码,需确保name已经过滤或转义 - 它不拦截开发者手动调用
header(),所以混合使用时容易绕过防护
自定义响应头必须手动过滤换行和控制字符
ThinkPHP 没有全局响应头过滤钩子,所有通过 response()->header()、header()、Response::create()->header() 设置的头,都需自行清理。
推荐做法是封装一个安全头设置函数:
function safe_header($key, $value) {
$value = str_replace(["\r", "\n", "\t", "\0"], '', $value);
$value = trim($value);
if ($value !== '') {
header($key . ': ' . $value);
}
}
或者更严格一点,只保留 ASCII 可见字符(0x20–0x7E):
$value = preg_replace('/[^\x20-\x7E]/', '', $value);
特别注意 Content-Disposition 场景,用户文件名常含中文或空格,不要简单用 rawurlencode(),而应按 RFC 5987 构造:
- 英文名:
Content-Disposition: attachment; filename="a.txt" - 中文名:
Content-Disposition: attachment; filename*=UTF-8''%E6%96%87%E4%BB%B6.txt
中间件中统一拦截危险头字段(可选但推荐)
如果你项目中大量存在动态响应头逻辑,可以在全局中间件中对 Response 对象做后置处理:
public function handle($request, \Closure $next)
{
$response = $next($request);
if ($response instanceof \think\Response) {
$headers = $response->getHeader();
foreach ($headers as $key => $val) {
if (is_array($val)) {
$val = end($val);
}
if (preg_match('/[\r\n\t\0]/', (string)$val)) {
// 记录日志或替换为空,避免直接暴露漏洞
$response->header($key, '');
}
}
}
return $response;
}
这个方案不能替代输入过滤,只是最后一道防线;且要注意性能开销,生产环境建议只对关键头(如 Location、Set-Cookie、Content-Disposition)做精确匹配检查。
真正难防的是那些藏在闭包、模板输出、第三方 SDK 回调里的隐式 header() 调用——它们往往绕过所有框架层,直接走 PHP 原生机制。上线前务必 grep 全项目 header( 和 setcookie(。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











