Knex.js 自动参数化 .where() 等链式方法传入的值,如 knex('users').where('email', userInput) 安全;但拼接字符串、误用 knex.raw() 或动态标识符未白名单校验(如表名、列名)则会绕过防护导致 SQL 注入。

Knex.js 本身不会自动防止所有 SQL 注入场景,关键在你怎么用 —— 所有用户可控的输入都必须进 .where()、.whereIn() 等参数化方法,绝不能拼接进字符串模板或 knex.raw()。
哪些写法是安全的(Knex 自动参数化)
Knex 对链式调用中传入的值默认做参数绑定,只要不脱离这个机制就基本安全:
-
knex('users').where('email', userInput)→ 安全,生成WHERE email = ?+ 参数数组 -
knex('posts').whereIn('id', [1, 2, 3])→ 安全,展开为WHERE id IN (?, ?, ?) -
knex('orders').whereBetween('total', [100, 500])→ 安全,等价于BETWEEN ? AND ? -
knex('logs').where('created_at', '>', new Date())→ 安全,Date 对象会被正确序列化
哪些写法是危险的(绕过参数化)
一旦你把用户输入塞进字符串或 knex.raw(),Knex 就不再帮你兜底:
-
knex.raw(`SELECT * FROM users WHERE email = '${userInput}'`)→ 危险,单引号可被闭合 -
knex('users').select('*').where(`email = '${userInput}'`)→ 危险,字符串条件跳过参数化 -
knex.raw('SELECT * FROM ?? WHERE ?? = ?', ['users', 'email', userInput])→ 前两个??是标识符占位符,但 Knex 不校验表名/列名合法性,若userInput是恶意字符串(如users; DROP TABLE users; --),仍可能触发注入 - 用
JSON.stringify()或模板字符串拼接 SQL 片段 → 危险,完全绕过 Knex 的绑定逻辑
动态列名、表名怎么办?
Knex 不提供内置的“安全标识符转义”函数,?? 占位符只是简单替换,不校验内容。真要支持动态标识符,得自己白名单过滤:
- 只允许匹配 /^[a-zA-Z_][a-zA-Z0-9_]*$/ 的字符串,否则抛错
- 维护一个预定义的列名映射表,比如
{ sort: 'created_at', dir: 'DESC' },再从里面取值 - 避免直接用 URL 查询参数(如
?order=users.email)构造列名;改用语义化键(如?sort=createdAt),服务端映射到真实字段 - 如果非用
knex.raw()拼表名,务必先过正则:if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) throw new Error('Invalid table name');
为什么 knex.raw() + 参数占位符还不够
knex.raw('SELECT * FROM ?? WHERE id = ?', ['users', 123]) 看似用了占位符,但第一个 ?? 是标识符插值,Knex 只做字面替换,不加引号也不转义 —— 如果传入的是 'users; DROP TABLE logs; --',最终 SQL 就是 SELECT * FROM users; DROP TABLE logs; -- WHERE id = 123,语句被截断执行。
真正安全的做法是:动态标识符必须白名单控制,动态值才交给 ? 占位符;二者不能混用同一输入源。
最常被忽略的一点:前端传来的排序字段、分组字段、JOIN 表别名,这些看起来“只是字符串”,但落到 SQL 里就是标识符上下文 —— 它们比 WHERE 值更难防护,也更容易被当成低风险而放行。


















