Sequelize在Node.js 20下默认安全,模型方法(findAll/update/destroy)自动参数化;高危点仅三处:sequelize.query()字符串拼接、literal()/fn()塞用户输入、动态列名/表名未白名单校验。

直接用模型方法,别碰 raw SQL
Sequelize 在 Node.js 20 下默认就是安全的,只要你不用 sequelize.query()、literal() 或 fn() 塞用户输入。所有模型调用——比如 Model.findAll()、Model.update()、Model.destroy()——都自动走参数化绑定,不需要你额外加 escape 或手动校验类型。
常见错误是看到搜索功能要动态字段,就写成:order: [[literal(`${req.query.sortBy} ${req.query.order}`)]]。这等于把控制权交给了攻击者,id; DROP TABLE users-- 这类输入会直接执行。
- 动态排序字段必须白名单校验:
['id', 'name', 'created_at'].includes(req.query.sortBy) -
attributes里写[[req.query.field, 'alias']]是列名注入,同样要白名单 - 函数包装字段改用
sequelize.col('username'),而不是literal(req.query.q)
sequelize.query() 必须用 replacements 或 bind
sequelize.query() 是唯一能绕过模型层的高危入口,它不检查输入,只认你传参方式。用模板字符串插值(哪怕加了 AS)或拼接字符串,一律算裸奔。
正确写法只有两种:
- 命名绑定:
sequelize.query("SELECT * FROM users WHERE status = :status", { replacements: { status: req.body.status } }) - 位置绑定:
sequelize.query("SELECT * FROM users WHERE id = ?", { replacements: [req.params.id] })
⚠️ 注意::status 和 ? 不能混用;replacements 和 bind 不能同时传;传错类型(比如把字符串当数字塞进 ?)可能触发类型不匹配错误,但不会导致注入。
原生驱动如 mysql2、pg 必须严格用占位符
如果你没用 ORM,直接连数据库,mysql2.query() 和 pg.client.query() 的占位符机制就是防注入的全部防线。拼字符串=开门揖盗。
- MySQL:占位符是
?,参数必须是数组,哪怕只有一个值:query("SELECT * FROM users WHERE id = ?", [req.query.id]) - PostgreSQL:只认
$1、$2这种数字位置参数,顺序必须和数组严格对应:query("SELECT * FROM logs WHERE level = $1 AND ts > $2", ['error', new Date(...)]) - SQL Server(node-mssql):用
@param,但记得ps.prepare()后要ps.unprepare(),否则连接池资源会泄漏
表名、字段名、ORDER BY 子句这些结构部分,数据库驱动根本不支持参数化,只能靠白名单硬编码,没有例外。
别信 escape(),它只是补救手段
mysql.escape() 或 connection.escape() 看起来方便,但它不是参数化查询,只是字符串转义。它对数字、布尔、日期等类型有固定规则,但一旦遇到嵌套对象、数组或特殊编码,行为可能不符合预期;而且它完全不管上下文——你在 WHERE 里用它,不代表在 ORDER BY 或 INSERT INTO ... VALUES 里也能照搬。
- 它不能替代占位符,尤其在复杂条件(如
IN (?))中,数组展开逻辑容易出错 - 日志记录、错误信息、监控埋点里如果还拼了原始 SQL 字符串,
escape()也救不了 - 真正难的不是写对一行
query(),而是确保所有分支路径(if/else、异常处理、调试输出)都没漏掉参数化
白名单校验 + 参数化占位符 + 模型方法优先,这三件事做全了,SQL 注入才真的退场。其他都是临时胶带。


















