应先urldecode()再basename()截断文件名,配合白名单校验、realpath()确认路径在授权目录内,并用move_uploaded_file()安全上传。

用 basename() 截断文件名再拼接,但必须先 urldecode()
用户提交的音频文件名(比如 $_FILES['audio']['name'])可能带 %2e%2e%2f 这类编码过的 ../,basename() 对它完全无效——它只认原始 ASCII 点和斜杠。所以第一步永远是:urldecode() 再 basename()。
常见错误现象:上传 test%2e%2e%2fetc%2fpasswd,没解码就 basename(),结果还是原样;解码后变成 test../etc/passwd,这时 basename() 才能正确返回 passwd。
- 务必在接收后立刻统一处理:
$safe_name = basename(urldecode($_FILES['audio']['name'])); - 加白名单校验:
preg_match('/^[a-zA-Z0-9_\-]{1,128}\.(mp3|wav|ogg|flac)$/i', $safe_name) - 拒绝空名、点号名:
if (!$safe_name || in_array($safe_name, ['.', '..'], true)) { die('Invalid filename'); }
realpath() 校验完整路径是否落在授权目录内
仅靠 basename() 不够。攻击者可能用符号链接、Unicode 全角点(如 .)、或绕过解析的组合方式欺骗你。最终落盘前,必须用 realpath() 确认物理路径真实归属。
关键点:返回值为 false 时必须拦截,且比对要用 !== 0,不是 == false。
- 定义绝对根目录:
define('AUDIO_ROOT', '/var/www/app/audio_storage');(不能是相对路径,也不能拼接用户输入) - 构造并解析:
$target = AUDIO_ROOT . '/' . $safe_name; $real_target = realpath($target); - 严格校验:
if ($real_target === false || strpos($real_target, realpath(AUDIO_ROOT)) !== 0) { die('Path traversal attempt'); }
上传必须用 move_uploaded_file(),且目标路径不拼接用户输入
$_FILES['audio']['tmp_name'] 是临时文件句柄,只能用 move_uploaded_file() 安全转移。任何用 fopen() + file_put_contents() 直接写入、或把 $_FILES['audio']['name'] 塞进路径字符串的操作,都等于开门揖盗。
性能影响:move_uploaded_file() 是 PHP 内置原子操作,比流式读写更安全也更快;它还会检查 tmp_name 是否真来自上传,防伪造。
- 目标路径只由两部分组成:预设绝对目录 + 经过上述过滤的
$safe_name - 别重命名?可以,但重命名逻辑也得走白名单校验,比如:
$new_name = uniqid('audio_') . '.' . pathinfo($safe_name, PATHINFO_EXTENSION); - 严禁出现类似
'uploads/' . $_FILES['audio']['name']这种写法,无论有没有basename()
Web 服务器层禁用执行权限 + 非 Web 可访问目录优先
即使代码层全做对了,如果音频目录在 Web 根目录下(如 /var/www/html/uploads/),且 Apache/Nginx 配置允许执行 PHP,攻击者上传 shell.jpg.php(利用解析漏洞)仍可能触发命令执行。
最容易被忽略的是:开发者常以为“只要扩展名不是 .php 就安全”,却忘了服务器配置、MIME 类型映射、甚至 .htaccess 覆盖规则这些外部因素。
- 最优解:把
AUDIO_ROOT设在 Web 根目录之外,例如/var/www/app/audio_storage,通过 PHP 脚本控制下载(用readfile()+header()) - 次优但必须做:若必须放 Web 目录内,用 Web 服务器配置禁止执行,例如 Nginx 中加
location ~ \.php$ { deny all; } - 别依赖
.htaccess:共享主机上可能被禁用,且容易被覆盖
realpath() 校验那一步的路径比对逻辑不被绕过——特别是当系统存在挂载点、bind mount、或容器内路径映射时,realpath() 的行为可能和预期不一致。这种环境差异,往往要到上线后被扫描器打中才暴露。php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











