Sequelize 的 where 条件值默认参数化安全,但字段名、literal() 内容及动态键名会绕过防护导致 SQL 注入;须用白名单校验列名,禁用 literal() 包裹用户输入,复杂条件中任一 literal() 都会破坏整体安全性。

Sequelize 的 where 条件默认安全,但一旦混入 literal()、动态键名或函数嵌套,立刻失效。
where 对象里的值天然参数化,但键名不是
Sequelize 把 where 对象中所有值(如 { name: 'alice' } 中的 'alice')自动转为绑定参数,无需额外操作。但键名(即字段名)是直接拼进 SQL 的,不受参数化保护。
- ✅ 安全:
where: { username: req.query.username }——username是写死的字段名,req.query.username是值,走参数化 - ❌ 危险:
where: { [req.query.field]: req.query.value }——req.query.field成为列名,SQL 中裸露无防护 - ✅ 替代方案:用白名单校验列名,例如
const validFields = ['name', 'email', 'status']; const field = validFields.includes(req.query.field) ? req.query.field : 'name'; where: { [field]: req.query.value }
别用 literal() 包用户输入进 where
literal() 的作用就是“原样输出”,它绕过所有参数化机制。只要里面出现 req.*,就等于把攻击面焊死在 SQL 里。
- ❌ 危险:
where: { [Op.or]: [fn('LOWER', literal(req.query.q))] }——req.query.q被当 SQL 片段执行 - ❌ 危险:
where: { status: { [Op.eq]: literal(`'${req.query.status}'`) } }—— 字符串插值 +literal()= 双重破防 - ✅ 正确:
where: { [Op.or]: [fn('LOWER', sequelize.col('username'))] },再配合{ username: { [Op.iLike]: req.query.q } }这类标准值匹配 - ⚠️ 注意:
fn('CONCAT', literal(req.query.prefix), sequelize.col('name'))同样危险——literal()出现在任意位置都无效
复杂条件组合时,警惕 Op 嵌套里的 literal()
开发者常以为用了 [Op.and] 或 [Op.or] 就安全了,其实关键看每个子项是否引入非参数化内容。
- ❌ 危险:
where: { [Op.and]: [ { id: { [Op.gt]: req.query.minId } }, literal(`updated_at > '${req.query.since}'`) ] }——literal()子项彻底破坏整条 where 安全性 - ✅ 安全替代:
where: { [Op.and]: [ { id: { [Op.gt]: req.query.minId } }, { updated_at: { [Op.gt]: req.query.since } } ] } - ⚠️ 特别注意:
sequelize.fn()和sequelize.col()是安全的,但sequelize.literal()不是;两者不能混用在同一个条件链中
最易被忽略的是:你以为只在 attributes 或 order 里才要防 literal(),其实 where 里用一次,整条查询就失去参数化保护。安全边界不在“用了什么方法”,而在“用户输入是否出现在 SQL 结构位(列名、函数体、表达式)而非纯值位”。

















