ThinkPHP 上传文件如何校验后缀防止 Webshell 上传

文章导读
最稳妥的方案是后端白名单校验后缀、重命名文件,并在服务器层面禁止上传目录执行 PHP 脚本。
📋 目录
  1. 快速处理思路
  2. 核心风险原理
  3. 分步落地实施
  4. 验证与排查
  5. 常见坑点
A A

最稳妥的方案是后端白名单校验后缀、重命名文件,并在服务器层面禁止上传目录执行 PHP 脚本。

先说结论:单纯依赖后端后缀校验不够,必须结合服务器配置禁止上传目录的脚本执行权限,才能有效防止 Webshell。

  • 先判断:确认业务是否真的允许上传非图片类文件,能禁则禁。
  • 优先做:代码层强制白名单校验 + 文件重命名,不使用原始文件名。
  • 再验证:尝试上传 .php/.phtml 等后缀,确认无法访问或执行。

快速处理思路

如果不方便立即修改服务器配置,先在代码层做严格限制。ThinkPHP 6 中建议使用 request()->file() 链式调用进行校验,示例如下:

$file = request()->file('file');
if ($file) {
    // 强制白名单校验扩展名
    $info = $file->validate(['ext' => 'jpg,png,gif'])->move('upload');
    // 务必重命名,不要保留原文件名
}

注意:仅靠代码校验无法完全防御解析漏洞,如果服务器没有禁止上传目录的 PHP 执行权限,攻击者仍可能通过特殊后缀或配置绕过。

核心风险原理

文件上传漏洞的核心在于服务器“信任”了用户上传的文件内容或后缀。常见的绕过方式有三种:

  • 后缀绕过:后端只检查了后缀名,但服务器配置允许解析 .php5、.phtml、.phar 等冷门后缀,或者存在大小写绕过(Windows 环境)。
  • 内容绕过:文件后缀是 jpg,但文件头包含了 PHP 代码,如果服务器配置不当,可能将其当作 PHP 执行。
  • 路径绕过:利用上传目录的遍历漏洞,将文件保存到可执行目录。

因此,单靠代码校验后缀是不够的,必须配合服务器权限控制,形成纵深防御。

分步落地实施

第一步:代码层白名单校验与完整示例

ThinkPHP 上传文件如何校验后缀防止 Webshell 上传

在 ThinkPHP 控制器中,不要使用黑名单,必须使用白名单。只允许业务需要的后缀。以下是包含验证、移动、重命名的完整控制器方法示例:

public function upload()
{
    $file = request()->file('file');
    if (!$file) {
        return json(['code' => 0, 'msg' => '未检测到上传文件']);
    }

    // 1. 强制白名单校验扩展名
    $info = $file->validate(['ext' => 'jpg,png,gif'])->move('upload');
    if (!$info) {
        return json(['code' => 0, 'msg' => $file->getError()]);
    }

    // 2. 强制重命名文件(hash 策略生成随机文件名)
    $savename = \think\facade\Filesystem::disk('public')->putFile('topic', $file, 'hash');
    
    return json(['code' => 1, 'url' => $savename]);
}

第二步:服务器禁止上传目录执行 PHP

这是最关键的一步。在 Nginx 或 Apache 配置中,明确禁止上传目录解析 PHP 脚本。

Nginx 配置示例:

# 注意:/upload/ 路径需根据 config/filesystem.php 中的实际目录修改
location ~* /upload/.*\.(php|php5|phtml|php7)$ {
    deny all;
}

Apache 可在上传目录放置 .htaccess:

<FilesMatch "\.(php|php5|phtml)$">
    Order Deny,Allow
    Deny from all
</FilesMatch>

验证与排查

完成配置后,不要直接上线,先在测试环境验证。

  1. 尝试上传一个内容为 <?php phpinfo();?> 的文件,后缀改为 .php。观察系统是否拦截。
  2. 如果系统允许上传(例如后缀被改成 .jpg),尝试在浏览器访问该文件 URL。如果显示空白或下载,说明服务器未执行 PHP;如果显示 phpinfo 信息,说明服务器配置未生效,存在风险。
  3. 检查上传目录的权限,确保 Web 服务器用户只有写入权限,没有执行权限。

常见坑点

  • 前端校验不可信:JavaScript 做的后缀检查只能防误操作,攻击者可以绕过前端直接发包,后端必须二次校验。
  • 大小写与空格:Windows 服务器对大小写不敏感,.Php 可能也能执行;部分旧版本 PHP 对文件名末尾的点或空格处理不当,需确保存储时清理了这些字符。
  • 二次渲染缺失:如果是图片上传,最好经过 GD 库或 ImageMagick 二次渲染,这样可以去除图片中嵌入的恶意代码。
  • 独立域名存储:条件允许的话,将上传文件存储到独立的静态域名或 OSS 对象存储,彻底隔离脚本执行环境。