必须用 mysql2 的 execute() 而非 query() 处理用户输入,因 execute() 强制预编译防 sql 注入,query() 即使含 ? 占位符若参数未正确包裹仍不安全;动态结构(表名、列名)须白名单校验,禁用 literal() 和字符串拼接。

mysql2 的 execute() 必须用,query() 要谨慎——Node.js 20 本身不防 SQL 注入,靠的是你用对数据库驱动的调用方式。
用 execute() 替代 query() 处理用户输入
Node.js 20 只是运行时环境,不自带 SQL 安全机制。mysql2(推荐 v3+)在 v20 下默认启用预编译语句,但前提是必须显式调用 execute();query() 仍走字符串拼接路径,即使用了 ? 占位符,若参数未进数组或对象绑定层,照样不安全。
-
execute()强制走预编译:传入 SQL 字符串 + 参数数组,MySQL 服务端会先编译语句模板,再绑定值,攻击者无法改变语法结构 -
query()默认不预编译:即使写"WHERE id = ?",若你传参写成query(sql, { id: req.query.id })(命名绑定但驱动未配置namedPlaceholders: true),参数可能被忽略,回退为字符串拼接 - 常见错误:
pool.query("SELECT * FROM users WHERE name = ?", req.body.name)—— 少了包裹参数的[],req.body.name被当回调函数传进去,直接报错或执行空查询
动态表名/列名必须白名单硬控制
? 和 execute() 只能保护数据值,不能保护 SQL 结构。任何拼进 SQL 字符串的 req.query.table、req.body.sortBy、req.params.field 都是高危点。
诊断并恢复通过 SSH 隧道连接的 OpenClaw 节点。用于解决配对必需错误、隧道冲突、远程端点错误以及 SSH 目标配置错误等问题。
- 错误写法:
`ORDER BY ${req.query.sortBy}`或sequelize.col(req.query.field)—— Joi 校验string().max(32)挡不住id; DROP TABLE users-- - 正确做法:定义允许列表,严格比对
const allowedFields = ['id', 'name', 'email', 'created_at'];if (!allowedFields.includes(req.query.sortBy)) throw new Error('Invalid sort field'); - 别信“转义就够了”:
mysql2.escapeId(req.query.table)只防单引号闭合,不防关键字注入(如传users; DROP TABLE logs),仍需白名单兜底
禁用 Sequelize.literal() 和所有运行时字符串拼接
只要字符串里出现 ${}、+、req.*,literal() 就等于把 SQL 注入开关焊死在代码里。
- 危险组合:
order: [[literal(`${req.query.sortBy} ${req.query.order}`)]]—— 用户可传id ASC, (SELECT password FROM admins LIMIT 1) - 安全替代:
order: [[req.query.sortBy, req.query.order === 'desc' ? 'DESC' : 'ASC']],且sortBy已经过白名单校验 - 连
fn('LOWER', literal(req.body.q))都不行 ——literal()的参数必须是静态字符串,比如literal('NOW()')或literal("'active'")
配置文件里绝不存 SQL 片段或模板
Node.js 加载 config.json 或 config.js 是同步读取+解析,不经过任何安全检查。一旦配置含占位符或拼接逻辑,就等于把注入入口部署到磁盘上。
- 错例:
"getUser": "SELECT * FROM users WHERE email = '${email}'"→eval()或模板引擎一渲染就是裸注入 - 错例:
module.exports = { sql: `SELECT * FROM ${process.env.TABLE_NAME}` };→ 环境变量被 CI/CD 注入后直接 RCE - 正例:配置只存连接信息(
DB_HOST,DB_PORT)和元数据(allowedTables: ["users", "orders"]),SQL 字符串写死在 service 层或由 ORM 模型生成
真正卡住 SQL 注入的,永远是那一行 execute(sql, [value]) 和它背后强制的预编译链路;其余所有校验、转义、白名单,都是为了确保这行代码能跑起来——而不是让它变成摆设。










