模板字符串拼接sql是sql注入高危入口,因${}仅作字符串拼接而非参数化;sql-template-strings可缓解但需配合预编译驱动且表名/字段名等动态部分须白名单校验。

因为模板字符串拼接 SQL 就是裸写 SQL 注入入口,Node.js 不会拦截、不转义、不警告,执行时直接把攻击 payload 塞进数据库。
模板字符串 ${} 不是参数化,只是字符串拼接
JavaScript 的 `SELECT * FROM users WHERE id = ${id}` 看起来像“带参数”,但 ${id} 在模板解析阶段就被求值并转成字符串,然后硬塞进 SQL 字符串里。它和 'SELECT * FROM users WHERE id = ' + id 完全等价。
- 如果
id = "1 OR 1=1 --",最终语句变成:SELECT * FROM users WHERE id = 1 OR 1=1 -- - 如果
id = "1'; DROP TABLE users; --",只要后端用的是query()且没设严格模式,就真可能删表 - 这种拼接发生在 JS 执行层,数据库驱动(如
pg或mysql2)拿到的已经是完整恶意字符串,连预编译机会都没有
sql-template-strings 为什么能缓解,但不是万能解药
sql-template-strings 的作用是强制拆开结构与数据:它把 sql`WHERE name = ${name}` 编译成 { text: "WHERE name = ", values: [name] },再交给 pg.query() 或 mysql2.execute() 去做真正的参数绑定。
- ✅ 正确用法:
client.query(sql`SELECT * FROM users WHERE email = ${email}`)——email进values数组,不出现在text中 - ❌ 错误用法:
sql`SELECT * FROM ${tableName} WHERE id = ${id}`—— 表名无法参数化,${tableName}直接拼进text,白名单校验必须另做 - ⚠️ 注意:
sql-template-strings本身不连接数据库、不执行 SQL,只做语法切分;你仍需确保下游驱动真正支持并启用了预编译(比如mysql2.execute(),而非query())
动态字段名(如 ORDER BY、GROUP BY)必须白名单校验
数据库不支持用 $1 或 ? 占位符替换列名、表名、函数名或关键字。一旦你把用户输入放进这些位置,就等于放弃参数化保护。
- ❌ 危险:
ORDER BY ${req.query.sort}或SELECT ${req.query.fields} FROM users - ✅ 必须白名单:
const allowedSort = ['created_at', 'name', 'email']; if (!allowedSort.includes(req.query.sort)) throw new Error('Invalid sort'); - ✅ 更稳妥加正则兜底:
/^[a-zA-Z0-9_]+$/拒绝点号、空格、反引号、分号等非法字符 - ⚠️ ORM 用户注意:Sequelize 的
attributes: [[req.query.sort, 'alias']]同样危险——别名不解决列名注入,照样得白名单
最易被忽略的一点:安全不取决于“有没有用模板字符串”,而取决于“值是否脱离 SQL 字符串上下文”。只要用户输入出现在单引号/双引号/反引号包围的 SQL 文本里,哪怕只出现一次,风险就已经成立。











