yii框架默认使用pdo预处理防止sql注入,where()等方法安全因自动绑定参数;但手动拼接sql、未校验表名/排序字段、误用expression或未处理数组参数等会引发风险。

Yii 框架本身不会因你写了 where(['name' => $userInput]) 就产生 SQL 注入——它默认走 PDO 预处理,参数天然隔离。但「防不住」的情况,几乎都出在开发者绕过框架封装、手动拼 SQL 字符串,或误以为某些写法“自动安全”。
用 Query Builder 或 ActiveRecord 时,where() 等方法为什么安全
这些方法内部会把数组键值对转成带占位符的预处理语句,并调用 PDO::prepare() + bindValue()。即使 $input 是 "admin' OR '1'='1",最终执行的也是:
SELECT * FROM user WHERE username = ?
而 ? 被绑定为一个完整字符串值,数据库不会解析其中的单引号逻辑。
- 支持嵌套条件:
where(['AND', ['status' => 1], ['LIKE', 'name', $q]]),所有变量仍走绑定 -
andFilterWhere()和orFilterWhere()同样安全,空值会被自动忽略,不生成对应条件 - 模糊查询也安全:
['LIKE', 'email', '%'.$domain]中的$domain仍被绑定,不是字符串拼接
原生 SQL 场景下,createCommand() 怎么正确绑定参数
一旦你用了 createCommand($sql),安全责任就完全落到你身上。错误写法是直接插值:
// ❌ 危险:字符串拼接 $sql = "SELECT * FROM post WHERE title = '" . $_GET['t'] . "'";
正确方式必须显式使用占位符 + 绑定:
- 命名参数(推荐):
bindValue(':title', $title, PDO::PARAM_STR) - 位置参数:
bindValue(1, $title, PDO::PARAM_STR),注意索引从 1 开始 - 批量绑定:
bindValues([':id' => 123, ':status' => 'draft']) - 别用
bindParam()除非真要传引用;普通场景一律用bindValue()
占位符名不能来自用户输入(比如 ':'.$userCol),否则等于开后门。
哪些写法看似安全,实则可能踩坑
框架封装再厚,也挡不住开发者“自己拆墙”:
-
createCommand("SELECT * FROM {$table} WHERE id = :id")——$table若未白名单校验,就是表名注入 -
orderBy($_GET['sort'])—— 排序字段不能直接进,得映射到白名单:in_array($sort, ['created_at', 'name'], true) ? $sort : 'id' -
andFilterWhere(['IN', 'status', $_GET['statuses']])—— 如果$_GET['statuses']是字符串"1,2,3",会查status IN ('1,2,3'),而非三个值;应先explode(',', $_GET['statuses']) - 用
yii\db\Expression时,里面的内容不经过绑定,new Expression("NOW() + INTERVAL $days DAY")的$days必须提前校验为整数
参数绑定和类型安全的关系
绑定只保证 SQL 结构安全,不负责 PHP 类型转换。例如:
-
bindValue(':id', $_GET['id'], PDO::PARAM_INT)不会把"123abc"截断成123,PDO 可能报错或静默转成0 - 更稳妥的是先过滤:
$id = (int) $_GET['id'];或用filter_var($_GET['id'], FILTER_VALIDATE_INT) - 对于字符串,
PDO::PARAM_STR足够;但业务上仍需长度、正则等校验,比如邮箱不能只靠绑定防注入
真正容易被忽略的,是那些“半托管”场景:排序、分表名、动态字段名——它们不在绑定覆盖范围内,却常被当成普通参数处理。











