贝叶斯先验是认知的杠杆支点,本质为信念度量;其兼具起点与囚笼双重性,须避免零先验或僵化先验,恪守克伦威尔法则保留被证伪缝隙。

Content-Type 必须匹配真实文件类型,不能靠扩展名硬写
很多开发者直接根据 $filename 后缀写死 header('Content-Type: image/png'),但攻击者只要把恶意 PHP 文件重命名为 shell.png,就能绕过检查并执行代码。真实 MIME 类型必须由内容决定。
正确做法是用 finfo_open() 动态检测:
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file) ?: 'application/octet-stream';
finfo_close($finfo);
header('Content-Type: ' . $mimeType);
- 不要依赖
pathinfo($file, PATHINFO_EXTENSION)做 switch 判断 - 如果
finfo_file()返回空或异常,fallback 到application/octet-stream,避免浏览器嗅探 - 注意:
finfo_open()在某些共享主机上可能被禁用,需提前检查
Content-Disposition 中文文件名要分浏览器兼容处理
直接写 filename="报表.xlsx" 在 Chrome/Firefox 会乱码或截断;只写 filename*=UTF-8''... 又会让旧版 Safari 拒绝下载。RFC 5987 要求双写。
安全写法是同时提供两个字段:
立即学习“PHP免费学习笔记(深入)”;
$filename = '销售报表_2026Q2.xlsx';
$encoded = rawurlencode($filename);
header('Content-Disposition: attachment; filename="' . mb_substr($filename, 0, 100, 'UTF-8') . '"; filename*=UTF-8\'\'' . $encoded);
-
filename=部分限制长度(建议 ≤100 字符),且不编码,供老浏览器兜底 -
filename*=必须用rawurlencode(),不是urlencode()—— 后者会把中文转成+%E4%BD%A0格式,部分浏览器不认 - 不要用
mb_encode_mimeheader(),它生成的是邮件头格式(=?UTF-8?B?...?=),HTTP 头不支持
大文件下载必须禁用输出缓冲并流式输出
用 readfile() 加 ob_end_clean() 是常见错误组合:如果之前有 ob_start() 未关闭,ob_end_clean() 会清空缓冲区但不阻止后续输出,仍可能触发 “headers already sent”。
更稳妥的做法是主动关闭所有缓冲层:
if (ob_get_level()) {
while (ob_get_level()) ob_end_clean();
}
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
- 不要在下载逻辑前后 echo/print/HTML 输出,包括配置文件里意外的空行或 BOM
- PHP-FPM 环境下,
output_buffering可能在 php.ini 中设为 4096,必须显式关掉 - 超大文件(>2GB)注意
filesize()在 32 位系统返回 -1,改用stat($file)['size']更可靠
路径遍历漏洞比你想象中更容易触发
你以为过滤了 ../ 就安全?用户传 ....//、%2e%2e%2f、.\.\ 或 Unicode 归一化后的点号,都可能绕过简单 str_replace()。
真正安全的路径校验只有两种方式:
- 用
basename($_GET['file'])提取纯文件名,再拼到白名单目录后 —— 最简最有效 - 用
realpath()解析完整路径,再用strpos()检查是否仍落在允许目录内(注意返回 false 时要拒绝) - 永远不要把用户输入拼进
include、file_get_contents或 shell 命令中
文件必须存放在 Web 根目录之外,比如 /var/www-data/private-downloads/,而不是 /var/www/html/uploads/ —— 否则一个配置失误就等于裸奔。



















