save() 方法本身不导致 SQL 注入,但安全性取决于调用方式与数据校验;TP6.x 中需严格限制字段白名单、禁用字符串拼接 where 条件、配合验证规则与类型约束。

save() 方法本身不直接导致 SQL 注入,但是否安全,完全取决于你如何调用它、传入什么数据、以及模型层是否做了有效约束。
ThinkPHP 并没有 8.0 版本——截至 2026 年 10 月,官方最新稳定版是 ThinkPHP 6.3.x(支持 PHP 8.0–8.3)。你实际使用的很可能是 TP6.x 运行在 PHP 8.0+ 环境下。下面以 TP6.x 的 save() 行为为准分析。
save() 的安全前提:必须走模型字段自动映射
save() 是模型(Model)方法,它的安全性建立在「只允许写入已定义的数据库字段」这一机制上:
-
✅ 安全调用方式(推荐):
立即学习“PHP免费学习笔记(深入)”;
PHP 8.5.12下载修复 Core bailout 状态损坏安全问题,以及 BZ2、DOM、PDO、Opcache/JIT 内存与执行错误,改进 Unicode 字素处理、URL 解析、TLS 流状态、信号分发及会话错误处理。
- 实例化模型后赋值再保存:
$user = new User(); $user->name = input('name'); // 自动过滤?不一定,看验证配置 $user->email = input('email'); $user->save(); // 字段白名单控制,非定义字段不会入库 - 或使用
data()+save():$user = new User(); $user->data([ 'name' => input('name'), 'email' => input('email') ])->save();
- 实例化模型后赋值再保存:
-
❌ 危险调用方式(可能绕过防护):
- 直接传入用户可控的完整数组,且键名未校验:
$data = input('post.'); // 如提交了 ['name'=>'a','status'=>1,'table_name'=>'users'] $user = new User(); $user->data($data)->save(); // 若 $data 含非法字段(如 status=1 OR 1=1),但字段本身存在,仍可能参与 WHERE 或 UPDATE - 更危险的是配合
where()动态拼接:$user = new User(); $user->save(['id' => input('id')], ['id' => input('id')]); // 第二个参数是 where 条件 // 如果这里写成 ['id' => input('id') . ' OR 1=1'],就构成注入
- 直接传入用户可控的完整数组,且键名未校验:
关键风险点不在 save() 本身,而在条件构造环节
save($data, $where) 支持第二个参数作为更新条件。若 $where 是用户输入拼接的字符串或未校验数组键名,就极易出问题:
-
save($data, ['id' => input('id')])→ 安全(绑定) -
save($data, 'id = ' . input('id'))→ 高危!字符串拼接,直接注入 -
save($data, [input('key') => input('val')])→ 高危!键名可控,可覆盖 status、is_delete 等逻辑字段
模型层防护不是万能的
TP6 的模型默认开启字段类型检测和自动转换(如 int 字段会强转),但这只对已声明的字段类型有效:
-
protected $type = ['id' => 'integer'];→input('id')会被转为整型,天然防注入 - 但若字段是
string类型,且没配验证规则,input('name')仍可能含' OR 1=1 --等内容,虽不会破坏 UPDATE 语句结构,却可能影响业务逻辑(如注入到日志、缓存键、HTML 输出等)
推荐做法
- 始终使用数组方式传参,避免字符串拼接 where 条件
- 对
save()的$where参数,统一用严格白名单字段(如只允许id、uuid) - 在模型中配置
validate或使用独立Validate类校验输入,尤其对排序、状态、类型字段做枚举限制 - 开启
app_debug = false,关闭调试模式下的 SQL 明文输出,防止敏感字段泄露 - 配合 Psalm 做静态分析,自动标记
save()调用中未经校验的input()污点传播
不复杂但容易忽略


















