只要正确使用 createcommand() 配合 bindvalue()(或 bindvalues())绑定参数,sql 注入即可有效防护;但动态字段名需白名单校验,in 条件须循环绑定占位符,且不可省略绑定步骤。

只要用对 createCommand() + bindValue()(或 bindValues()),SQL 注入就基本防住了;但若跳过绑定、拼字符串、或把用户输入当字段名用,再好的框架也拦不住。
createCommand() 必须配合 bindValue() 才防注入
Yii 的 createCommand() 本身不自动防注入——它只是创建命令对象,真正起作用的是后续的参数绑定。不绑定就直接执行,和裸写 SQL 没区别。
- ✅ 正确:
$cmd = Yii::$app->db->createCommand('SELECT * FROM user WHERE id = :id')->bindValue(':id', $id)->queryOne(); - ❌ 危险:
$cmd = Yii::$app->db->createCommand("SELECT * FROM user WHERE id = $id")->queryOne();(字符串拼接) - ⚠️ 注意:占位符必须带冒号前缀,
bindValue('id', $id)不生效,必须是bindValue(':id', $id) - ⚠️ 字符串类型建议显式声明:
bindValue(':name', $name, PDO::PARAM_STR),避免被误判为整数导致截断
动态排序/分组字段不能绑参数,必须白名单校验
ORDER BY、GROUP BY、表别名、字段别名这些语法结构,PDO 预处理无法参数化。用户输入一旦直接进这些位置,就是注入入口。
- ❌ 错误写法:
->orderBy($_GET['sort'] . ' DESC')或->groupBy($_GET['group']) - ✅ 正确做法:先白名单过滤,再构造数组条件:
if (!in_array($sort, ['created_at', 'price', 'name'], true)) { throw new BadRequestHttpException(); } ->orderBy([$sort => SORT_DESC]) - ⚠️ 连表别名也一样:
JOIN user u ON u.id = o.user_id中的u若来自用户输入,必须先校验是否在['u', 'o', 'p']白名单内
IN 条件不能 implode 拼字符串,得用循环绑定或占位符预生成
WHERE id IN (1,2,3) 这种场景常见,但 implode(',', $ids) 是典型漏洞点——攻击者可传 1,2); DROP TABLE user; --。
- ✅ 推荐方式一(循环绑定):
$placeholders = []; $params = []; foreach ($ids as $i => $id) { $placeholder = ':id_' . $i; $placeholders[] = $placeholder; $params[$placeholder] = (int)$id; } $sql = 'SELECT * FROM user WHERE id IN (' . implode(',', $placeholders) . ')'; $cmd = Yii::$app->db->createCommand($sql, $params)->queryAll(); - ✅ 推荐方式二(
bindValues()一次传入):$params = array_map(fn($id) => (int)$id, $ids); $placeholders = array_keys($params); // 实际需映射为 :id_0, :id_1…(更推荐方式一,逻辑清晰) - ❌ 禁止:
"WHERE id IN (" . implode(',', $ids) . ")"
ActiveRecord 的 where 数组语法虽安全,但仅限固定结构
User::find()->where(['status' => $status])->all() 安全,是因为底层强制走预处理;但一旦混入动态字段或字符串条件,防护立即失效。
- ✅ 安全:
User::find()->where(['username' => $input])->one()、->andFilterWhere(['like', 'name', $q]) - ❌ 危险:
User::find()->where("username = '$input'")->one()(字符串条件绕过绑定) - ❌ 危险:
User::find()->where(['status' => $status])->orderBy($userSort . ' DESC')($userSort未校验) - ⚠️
findBySql()完全不兜底:User::findBySql("SELECT * FROM user WHERE id = $id")->one()等同于裸拼 SQL
最常被忽略的其实是三件事:动态字段名没白名单、IN 条件图省事拼字符串、以及 createCommand() 写完忘了调 bindValue()。这三点在线上 Yii2 项目里,几乎就是 SQL 注入漏洞的“标准配方”。











