ActiveRecord 默认防 SQL 注入,推荐使用参数化查询如 where(['username' => $input]);混用数组与字符串写法、在 orderBy/groupBy 中直接使用用户输入、绕过 ActiveRecord 未绑定参数、Yii 1.x 使用已淘汰的 CDbCriteria 均存在风险。

ActiveRecord 默认就防 SQL 注入,别手拼 SQL
只要用 User::find()->where(['username' => $input]) 这类 ActiveRecord 写法,Yii 就自动走参数化查询,$input 里的 ' OR '1'='1 或 1; DROP TABLE users 全都会被当字符串值处理,不会触发语法解析。这是 Yii 最省心、最推荐的路径。
容易踩的坑:
- 在
where()里混用数组和字符串写法,比如where(['status' => 1, "name LIKE '%{$name}%'"])—— 后半段直接拼接,立刻破防 - 把用户输入塞进
orderBy()或groupBy(),例如orderBy($_GET['sort']),这些子句不支持参数绑定,必须白名单校验 - 误以为模型验证规则(如
required、string)能代替 SQL 层防护,其实它只管业务逻辑,不干预查询构造
原生 SQL 必须用 bindParam() 或 bindValue()
当你绕过 ActiveRecord、直接写原生 SQL(比如复杂联查、窗口函数),createCommand() 的参数绑定是唯一安全出口。写成 "WHERE id = :id" 再调 bindValue(':id', $id),PDO 层会严格区分语句结构和数据内容。
常见错误现象:
-
createCommand("SELECT * FROM user WHERE name = '$name'")—— 单引号包裹变量,等于敞开大门 - 用
bindParam()时传了变量引用但后续改了值,导致执行时绑定的是新值(适合循环复用命令场景,但多数情况用bindValue()更稳) - 占位符名含非法字符,比如写成
:user-id(短横线不合法),应改为:user_id
order by / group by / having 这些地方不能绑参,得靠白名单
SQL 标准规定排序字段、分组字段属于“结构部分”,不是数据值,所以 PDO 不允许对它们做参数绑定。你不能写 orderBy(':field'),运行会直接报错 SQLSTATE[HY093]。
正确做法只有两个:
- 硬编码白名单:比如
$allowed = ['created_at', 'price', 'name']; if (in_array($sort, $allowed)) { orderBy($sort); } - 映射表转换:把前端传的
sort=price_desc映射为内部安全字段'price DESC',再拼进 SQL(注意只拼预设组合,不拼原始输入)
别试图用 addslashes() 或正则过滤来“清理”排序字段——攻击者可以绕过,且维护成本高、易漏。
CDbCriteria 在 Yii 1.x 里也默认参数化,但已淘汰
如果你还在维护老项目,CDbCriteria 的 compare()、addCondition() 方法底层也是参数绑定,$criteria->compare('email', $_GET['e']) 是安全的。但要注意:addCondition("status = {$_GET['s']}") 这种字符串拼接写法依然危险。
更关键的是:Yii 1.x 已停止维护,官方不再发布安全补丁。哪怕代码写得再规范,基础组件存在未修复漏洞(比如旧版 CUrlManager 的路由解析缺陷)也会让 SQL 防护形同虚设。升级到 Yii 2.0+ 或 3.0 是刚性前提。
真正难缠的点不在怎么写,而在于——开发时觉得“这个接口只是查个列表,应该没问题”,结果测试阶段发现某个隐藏的 $_GET['join_table'] 被悄悄拼进了 SQL,这种非显式路径最容易漏检。


















