ThinkPHP 5.1.26 未修复批量赋值漏洞,仍存在 create()/saveAll() 绕过 allowField 的风险;需升级至 5.1.40+ 或 TP6.3+/TP8.0+,并手动加固白名单机制。

ThinkPHP 5.1.26 并不修复模型批量赋值漏洞,相反,它仍存在该类风险——真正需要警惕的是:5.1.26 并未解决 create()、saveAll()、allowField() 缺失或绕过导致的批量赋值漏洞。该问题在 5.1.x 系列中长期存在,官方直到 5.1.40+ 才加强默认白名单机制,而彻底收敛需升级至 TP6.3+ 或 TP8.0+(LTS 版本)。当前(2026年10月)生产环境若仍在用 5.1.26,必须手动加固。
确认是否真在用 allowField 控制字段
很多项目看似写了 allowField,实则逻辑无效:
-
allowField(true)等同于关闭白名单,等同于全开放(高危!) -
allowField([])或未传参,默认启用autoWriteTimestamp等内置字段,但不拦截用户传入的非法字段(如is_admin=1) - 模型中定义了
protected $allowField = ['name', 'email'];,但控制器调用时又显式传入allowField(),会覆盖模型配置
检查所有 create()、saveAll()、data()->save() 调用点,确认:
- 是否明确传入了非空数组形式的白名单,例如
->allowField(['name', 'email', 'status']) - 是否存在
$model->create(input())这类直接将全部用户输入塞入的写法
强制字段白名单 + 显式过滤
不要依赖“框架默认会拦”,必须每处批量操作都主动设限:
- 使用
create()时:$data = input('post.'); $allowed = ['title', 'content', 'category_id']; $filtered = array_intersect_key($data, array_flip($allowed)); $result = ArticleModel::create($filtered); - 使用
saveAll()时:$items = input('list/a'); $allowed = ['name', 'price', 'stock']; $cleaned = array_map(function($item) use ($allowed) { return array_intersect_key($item, array_flip($allowed)); }, $items); ProductModel::saveAll($cleaned);
禁用危险快捷方法,改用显式赋值create() 和 saveAll() 天然携带批量赋值语义,风险集中。更安全的做法是:
- 改用
new Model()+ 逐字段->save(),或 - 用
data()+allowField()+save()三段式控制:$model = new UserModel(); $model->data(input('post.'), 'update') ->allowField(['username', 'mobile', 'avatar']) ->save();注意:
allowField()必须在data()之后、save()之前调用,否则无效。立即学习“PHP免费学习笔记(深入)”;
全局兜底:重写模型基类的 create 方法
在基础模型(如 app\common\model\BaseModel)中统一拦截:
public static function create($data = [], $filter = false, $validate = false)
{
// 强制要求子类声明白名单
$class = get_called_class();
$whitelist = call_user_func([$class, 'getAllowFields']) ?: [];
if (empty($whitelist)) {
throw new Exception('Model ' . $class . ' missing allowField whitelist');
}
$data = array_intersect_key($data, array_flip($whitelist));
return parent::create($data, $filter, $validate);
}再让所有业务模型继承并实现 getAllowFields(),从架构层杜绝漏配。
验证修复是否生效
构造测试请求,尝试注入敏感字段:
- POST
/user/create带{"username":"test","is_admin":1,"status":99} - 若数据库中
is_admin或status字段被写入,说明白名单未生效 - 同时检查日志,确认异常是否被抛出而非静默忽略
不复杂但容易忽略



















