PHP上传音频时应先用finfo_buffer()获取MIME类型,再读取前12字节比对魔数(如WAV为52494646、FLAC为664C6143),最后白名单校验三者缺一不可。

PHP上传音频时怎么读取文件头魔数
音频文件的魔数不是靠猜的,得从二进制开头几个字节里硬读出来。PHP 用 fopen($path, 'rb') 以二进制只读方式打开临时文件,再用 fread() 读前 12 字节足够覆盖常见音频格式头(如 MP3 的 ID3v2、FLAC 的 fLaC、WAV 的 RIFF)。别读太多——多数音频魔数就藏在前 4–8 字节,读多了反而可能误判。
常见音频魔数示例:
- MP3:开头可能是
49 44 33(ID3 标签)或FF FB/FF F3(MPEG frame sync) - WAV:固定以
52 49 46 46(RIFF)开头 - FLAC:以
66 4C 61 43(fLaC)起始 - OGG:以
4F 67 67 53(OggS)开头
finfo_buffer() 和 finfo_file() 哪个更适合上传校验
必须用 finfo_buffer()。因为上传文件的临时路径($_FILES['audio']['tmp_name'])虽然存在,但你不能保证它一直可读——某些服务器或并发场景下,finfo_file() 可能因文件被清理或权限变化而失败;而 finfo_buffer() 直接处理内存中的原始字节,更稳定。
关键点:
立即学习“PHP免费学习笔记(深入)”;
- 先用
file_get_contents($_FILES['audio']['tmp_name'])拿到二进制内容(不是字符串!) - 确保没被自动转码:如果数据来自 base64 解码,检查是否含
\x00字节且未被截断 -
finfo_open(FILEINFO_MIME_TYPE)后必须传原始二进制,不能传 UTF-8 字符串
仅靠魔数比对为什么不够,还要结合 MIME 校验
魔数容易被构造性绕过——比如在合法 WAV 文件头部插入一段无害 ID3 标签,就可能让纯魔数匹配逻辑误判为 MP3;反过来,有些精简 FLAC 文件可能省略了部分头部字段,导致魔数不全。
所以真实校验链应该是:
- 先用
finfo_buffer()获取 MIME 类型(如audio/mpeg、audio/wav) - 再读取前 12 字节做魔数比对,交叉验证
- 最后白名单过滤:
in_array($mime, ['audio/mpeg', 'audio/wav', 'audio/x-flac', 'audio/ogg'])
三者缺一不可。只信 MIME?浏览器伪造的 type 字段早被弃用;只信魔数?攻击者可以手工拼一个“看起来像 WAV”的二进制头。
上传音频时最容易被忽略的执行风险点
音频文件本身不会执行,但攻击者常把 PHP 代码塞进 ID3 标签、APE 标签或 WAV 的 LIST chunk 里,等你用 include() 或 eval() 处理元数据时触发——这已经不是“上传漏洞”,而是“解析漏洞”。
安全底线:
- 上传目录必须禁用脚本执行(Nginx 加
location ~ \.(php|pl|py|jsp|sh|cgi)$ { deny all; },Apache 加Options -ExecCGI) - 绝不要用
exif_read_data()或第三方音频库解析元数据后再输出到页面——它们可能反序列化恶意字段 - 如果业务真要读 ID3,用专用库(如 getID3)并开启严格模式,跳过所有非标准标签
魔数和 MIME 是第一道门,目录权限和解析行为控制才是最后一道墙。漏掉任意一层,都可能让音频变成 WebShell 的运输载体。



















