sequelize模型方法默认参数化,但用户输入若用于列名、表名、排序字段等sql结构位则仍危险;sequelize.query()需显式使用replacements;literal()和fn()遇用户输入即失效;动态字段必须通过硬编码白名单校验。

Sequelize.findAll() 等模型方法默认就参数化,不用额外配置
只要用 Model.findAll()、Model.findOne()、Model.update()、Model.destroy() 这类标准方法,且所有用户输入都放在 where、attributes(值部分)、limit、offset 等“数据位置”,Sequelize 就会自动走预编译 + 参数绑定。不需要手动加 replacements 或转义。
常见错误是误以为“用了 ORM 就万事大吉”,结果把用户输入塞进 SQL 结构位:比如 where: { [req.query.field]: req.query.value } —— 方括号动态键名会让 req.query.field 变成裸列名,直接拼进 SQL;又比如 order: [[req.query.sortBy, 'DESC']] —— 排序字段无法参数化,一传就破防。
-
where: { email: req.query.email }✅ 安全:值被绑定为 ? 参数 -
where: { [req.query.col]: 'value' }❌ 危险:列名被当 JS 属性解析,最终生成WHERE user_input = 'value' -
attributes: ['id', req.query.include]❌ 危险:数组元素若为用户输入,会被当列名直插
sequelize.query() 必须显式用 replacements,不能字符串拼接
sequelize.query() 不像模型方法那样“开箱即用”,它默认不参数化。如果你写 sequelize.query(`SELECT * FROM users WHERE id = ${req.query.id}`),等于裸奔。
正确做法是用 replacements 选项,且只接受数组或对象形式的占位符:
sequelize.query('SELECT * FROM users WHERE status = ?', { replacements: ['active'] })sequelize.query('SELECT * FROM users WHERE id IN (:ids)', { replacements: { ids: [1, 2, 3] } })
注意:replacements 只处理“值”,不处理表名、列名、函数名。所以 SELECT * FROM ${req.query.table} 即使加了 replacements 也无效 —— 那里根本不是参数位。
literal() 和 fn() 是注入高危区,含用户输入就等于放弃防御
Sequelize.literal() 的设计目标就是“原样输出”,它绕过所有参数机制。只要里面出现 req.*、模板字符串、+ 拼接,就立刻失效。
-
where: { name: { [Op.like]: literal(`%${req.query.q}%`) } }❌ 破防:拼接后变成name LIKE '%admin'-- %' -
attributes: [[literal('NOW()'), 'ts']]✅ 安全:字符串完全静态,无变量 -
fn('LOWER', literal(req.query.q))❌ 危险:literal()包裹什么,就输出什么,函数不改变其本质
同理,fn() 本身不危险,但一旦它的参数是 literal() 或动态字符串,就等价于手写 SQL 片段 —— 而这不是 ORM 该干的事。
白名单校验必须落在 Controller,不能靠正则或数据库读取
排序字段、分组字段、动态列名这些无法参数化的位置,唯一靠谱的方案是硬编码白名单,在路由处理层做校验。
例如允许按 id、name、created_at 排序:
- 定义
const ALLOWED_SORT_FIELDS = ['id', 'name', 'created_at'](写死在代码里) - 取值前判断:
const sortField = ALLOWED_SORT_FIELDS.includes(req.query.sort) ? req.query.sort : 'id' - 再传入:
order: [[sortField, 'ASC']]
别用 req.query.sort.replace(/[^a-z_]/g, '') 这类“过滤”——攻击者早就能用 Unicode、大小写混用、反斜杠绕过;也别从数据库查出字段列表动态生成白名单——表结构可能变,且查询本身可能被注入影响结果。
真正难的不是写对那几行代码,而是每次加新功能时,都得问一句:“这个变量,最后会出现在 SQL 的结构位,还是纯值位?” 问错一次,前面所有参数化都白搭。











