网络表单文件验证实现检查清单
分类:安全·实现
文件上传功能的实现涉及众多安全考虑,也存在许多容易被忽视的陷阱。本文解释了在生产环境中安全实现文件上传所需的检查清单。
清单概述
此清单主要关注后端(服务器端)验证。前端验证作为辅助的用户体验改进而实现,但不提供安全保障。
1. 文件大小验证
- 上传限制以字节为单位定义(不混淆 MB 和 MiB)
- 对于 PHP,已配置
upload_max_filesize和post_max_size - 对于 Nginx,
client_max_body_size包括 multipart 开销的余量 - 当
$_FILES['file']['error']为 UPLOAD_ERR_INI_SIZE / UPLOAD_ERR_FORM_SIZE 时,有错误处理 - 有最小文件大小检查(排除 0 字节文件)
$maxBytes) {
throw new \RuntimeException(sprintf(
'ファイルサイズ(%s)が上限(%s)を超えています',
number_format($file['size']),
number_format($maxBytes)
));
}
}
2. 文件格式验证(MIME 类型)
- 不信任从客户端发送的
Content-Type($_FILES['file']['type']) - 使用
finfo/mime_content_type()执行服务器端 MIME 类型验证 - 定义了允许的 MIME 类型的白名单
file($file['tmp_name']);
if (!in_array($mimeType, $allowed, true)) {
throw new \RuntimeException('許可されていないファイル形式です: ' . $mimeType);
}
3. 文件扩展名验证
- 定义了文件扩展名的白名单(白名单,而不是黑名单)
- 双扩展名(例如
shell.php.jpg)被检测并拒绝 - 在验证过程中应用大小写规范化(将
.JPG和.jpg视为相同)
2) {
throw new \RuntimeException('不正なファイル名です');
}
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
if (!in_array($ext, $allowed, true)) {
throw new \RuntimeException('許可されていない拡張子です: ' . $ext);
}
4. 魔数(文件签名)验证
- 对关键文件(例如排除可执行文件)验证魔术字节
5. 保存目标和文件名的安全处理
- 保存目录在 Web 根目录之外(或通过 XSendFile/X-Accel-Redirect 控制)
- 保存的文件名是随机生成的(例如 UUID),而不是原始文件名
- 路径遍历(例如
../../../etc/passwd)通过验证被消除 - 保存目录没有 PHP 执行权限(通过
.htaccess或 Nginx 配置禁用 PHP 处理)
6. 错误处理和响应
- 上传成功时返回适当的 HTTP 状态代码 (200/201)
- 超出大小时返回 413 Payload Too Large
- 对于无效的文件格式,返回 422 Unprocessable Entity
- 错误消息不包含服务器内部信息(路径、版本等)
7. 测试用例
实现后,请运行以下测试用例以验证行为。您可以使用 DevLab 中可用的测试文件。
| 测试用例 | 预期结果 | 要使用的文件 |
|---|---|---|
| 恰好在限制大小的文件 | 成功 | 阈值文件 |
| 超过限制 1 字节的文件 | 413错误 | 阈值文件 |
| 0字节的空文件 | 验证错误 | 手动创建 |
扩展名被伪装的文件 (PHP 伪装成 .jpg) | MIME 错误 | 损坏文件 |
| 标头损坏文件 | 验证错误 | 损坏文件 |
总结
实现安全的文件上传需要在多个层级进行验证。特别是,必须实现以下三点。
- 服务器端 MIME 类型验证(
finfo使用)— 不信任客户端申报 - 使用随机文件名保存 — 不使用原始文件名
- 禁用上传目录中的 PHP 执行——防止脚本在上传目录中执行
本文中可用的测试文件
相关文章
❓ 常见问题
文件上传校验最起码要做到哪些?
至少三点:在服务端用 finfo 校验 MIME 类型而非信任客户端申报;使用随机生成的文件名保存而非原文件名;在上传目录中禁用 PHP 执行,确保放入的文件无法运行。
为什么只检查扩展名不够?
扩展名极易伪造,无法识别 shell.php.jpg 这类双扩展名的恶意文件。应在扩展名白名单之外,结合 finfo 的 MIME 校验与文件头魔术字节校验。
文件上传应返回哪些 HTTP 状态码?
成功时返回 200 或 201,文件超出上限时返回 413 Payload Too Large,格式非法时返回 422 Unprocessable Entity。注意错误信息中不要包含服务器内部信息(路径、版本等)。