$_FILES数组是PHP上传文件的唯一入口,为二维嵌套结构,含name、type、size、tmp_name、error五个固定键;需校验error为UPLOAD_ERR_OK、清洗name、用finfo_file验证MIME、is_uploaded_file校验tmp_name合法性后才可move_uploaded_file。

$_FILES数组结构和字段含义必须看懂
PHP 接收上传文件后,$_FILES 是唯一入口,但它不是扁平数组,而是二维嵌套结构——每个表单字段名(如 file)对应一个子数组,含 name、type、size、tmp_name、error 五个固定键。漏掉任一字段都可能引发逻辑断裂。
常见误解是把 $_FILES['file']['type'] 当作可信 MIME 类型校验依据,其实它完全由浏览器提供,可被任意篡改;$_FILES['file']['name'] 也含原始路径信息(如 C:\fakepath\test.jpg),需用 basename() 清洗;tmp_name 是唯一能用于后续操作的合法临时路径,其他字段仅作参考。
-
error值为UPLOAD_ERR_OK(即 0)才表示上传流程走通,其余值均需拦截并返回明确错误 -
size单位是字节,不是 KB 或 MB,做大小限制时别写错换算 - 多文件上传时,
$_FILES['files']结构会变成「字段名 → 字段属性 → 索引数组」,例如$_FILES['files']['tmp_name'][0],不是$_FILES['files'][0]['tmp_name']
move_uploaded_file() 必须配合 is_uploaded_file() 使用
直接对 $_FILES['xxx']['tmp_name'] 调用 move_uploaded_file() 是危险操作。PHP 要求该文件必须是经 HTTP POST 上传产生的临时文件,否则函数静默失败——既不报错也不移动,容易误判为“上传成功”。
正确做法是先用 is_uploaded_file() 做合法性校验,再执行移动:
立即学习“PHP免费学习笔记(深入)”;
if ($_FILES['avatar']['error'] === UPLOAD_ERR_OK
&& is_uploaded_file($_FILES['avatar']['tmp_name'])) {
$dest = '/var/www/uploads/' . uniqid() . '_' . basename($_FILES['avatar']['name']);
if (move_uploaded_file($_FILES['avatar']['tmp_name'], $dest)) {
// 成功
}
}
- 跳过
is_uploaded_file()检查,攻击者可通过构造 POST 请求绕过表单,直接提交恶意路径(如/etc/passwd)触发 LFI 风险 -
move_uploaded_file()不支持跨文件系统移动,目标路径所在分区必须与临时目录在同一挂载点,否则失败 - 移动后
tmp_name对应文件自动销毁,不可重复使用
upload_max_filesize 和 post_max_size 的协同关系
PHP 文件上传失败,80% 以上源于配置冲突。upload_max_filesize 控制单个文件上限,post_max_size 控制整个 POST 请求体上限(含所有字段+文件)。若后者小于前者,哪怕文件本身很小,也会卡在解析阶段,$_FILES 为空且 $_POST 也收不到数据。
典型错误配置:
-
upload_max_filesize = 8M,但post_max_size = 2M→ 上传任何大于 2M 的请求直接 400 或空白响应 - 修改 php.ini 后未重启 PHP-FPM 或 Apache,配置不生效
- 使用
ini_set()动态修改这两个值无效(它们属于「PHP_INI_SYSTEM」级别,运行时不可改)
验证方式:打印 ini_get('upload_max_filesize') 和 ini_get('post_max_size'),确保后者 ≥ 前者,且单位统一(建议全用 M 或全用 B)。
前端 MAX_FILE_SIZE 隐藏域只是辅助,不能替代服务端校验
<input type="hidden" name="MAX_FILE_SIZE" value="5242880"> 这个字段作用极有限:它只在浏览器端做初步拦截,且极易被绕过(删掉 HTML 或用 curl 直传)。一旦依赖它做唯一限制,上传超限文件时 $_FILES['file']['error'] 会是 UPLOAD_ERR_FORM_SIZE(值为 2),但此时请求已进 PHP,仍需处理。
- 它必须放在
<input type="file">之前,否则无效 - 它的值单位是字节,不是 KB 或 MB,写错会导致前端拦截失效
- 真正可靠的大小控制,只能靠服务端读
$_FILES['file']['size']并比对阈值
安全边界必须落在 PHP 层:检查 error、清洗 name、验证 size、重验 MIME(用 finfo_file() 而非 type 字段)、确认 tmp_name 合法性、最后才移动。



















