sequelize.literal() 会绕过所有参数化防护,仅适用于完全静态sql片段;若含用户输入、拼接或变量则导致sql注入,应改用白名单校验、sequelize.col()、fn()或replacements等安全方案。

Op.literal 一用就绕过所有参数化防护
Sequelize.literal() 的作用是把传入字符串原样塞进 SQL,不加引号、不转义、不绑定——它根本不是为用户输入设计的。只要里面出现 req.query.q、req.body.sortBy 这类不可信数据,就等于把攻击面焊死在查询里。
常见破防写法包括:where: { [Op.or]: [fn('LOWER', literal(req.query.q))] }、attributes: [[literal(req.query.calc), 'computed']]、order: [[literal(`${req.query.sortBy} ${req.query.order}`)]]。这些看着像“动态功能”,实则是 SQL 注入的快捷入口。
literal() 的合法边界非常窄
它只适合写死的、无任何变量参与的 SQL 片段。一旦字符串里出现 ${}、+ 拼接、或来自 req.* 的值,就立刻掉出安全区。
-
attributes: [[literal('NOW()'), 'created_at']]✅ —— 字符串完全静态 -
where: { status: { [Op.eq]: literal("'active'") } }✅ —— 连单引号都是硬编码 -
order: [[literal('updated_at DESC')]]✅ —— 整个排序表达式不含变量
替代 literal() 的安全方案
绝大多数你以为“必须用 literal()”的地方,其实都有参数化路径:
- 需要动态列名?→ 白名单校验:
['id', 'name', 'email'].includes(req.query.sortBy),再用于order: [[req.query.sortBy, 'ASC']] - 需要函数包装字段?→ 用
fn('LOWER', sequelize.col('username')),别把用户值塞进fn()第二参数 - 需要复杂计算字段?→ 改用
sequelize.cast()、sequelize.fn()组合已知字段,或拆到应用层计算 - 真要拼 SQL 表达式?→ 改用
sequelize.query()+replacements,SQL 字符串保持静态,变量全走绑定层
复杂条件中 literal() 是“单点击穿”漏洞
开发者常以为用了 [Op.and] 或 [Op.or] 就安全了,其实关键看每个子项是否引入非参数化内容。哪怕其他条件都合规,只要其中一项是 literal(`updated_at > '${req.query.since}'`),整条 where 就彻底失效。
正确做法是统一转为参数化结构:{ updated_at: { [Op.gt]: req.query.since } }。注意:sequelize.fn() 和 sequelize.col() 是安全的,但 sequelize.literal() 不是——两者不能混用。
最容易被忽略的是嵌套层级里的 literal():比如 fn('CONCAT', literal(req.query.prefix), sequelize.col('name')),只要 literal() 出现在任意位置,参数化就归零。











