必须使用参数化查询(?占位符),这是防止SQL注入的唯一可靠方式;表名列名等标识符须用escapeId()转义或白名单校验;数据库账号应遵循最小权限原则;错误信息严禁暴露给前端。

直接用参数化查询,其他都是补救措施。不这么做,光靠过滤、转义、WAF,迟早出事。
MySQL Node.js 客户端必须用 connection.query() 的 ? 占位符
Node.js 环境下,mysql2 或官方 mysql 包都支持 ? 占位符语法,这是唯一能保证值被安全处理的方式。它不是“推荐”,而是强制路径——所有用户输入进 SQL 的地方,都得走这条路。
- ✅ 正确:
connection.query('SELECT * FROM users WHERE id = ?', [userId], callback) - ❌ 危险:
connection.query('SELECT * FROM users WHERE id = ' + userId)(哪怕 userId 是数字,也别拼) - ⚠️ 注意:
?只保护值,不能用于表名、列名、ORDER BY 字段——这些必须走白名单校验
动态表名或列名必须用 connection.escapeId() 处理
当你不得不拼接标识符(比如按时间分表、多租户切换 schema),escapeId() 是唯一靠谱的内置方法。它会把输入包裹在反引号里,并转义内部的反引号,防止注入 user`; DROP TABLE users; -- 这类 payload。
- ✅ 安全:
const sql = `SELECT * FROM ${connection.escapeId(tableName)}` - ❌ 无效:
connection.escape()不能用于表名——它只转义字符串值,对标识符无意义 - ? 更稳妥的做法是:提前定义允许的表名列表,运行时做白名单比依赖转义更可靠
连接账号必须遵循最小权限原则
即使代码全用参数化查询,一旦数据库账号有 DROP、CREATE、FILE 权限,攻击者拿到查询执行权后仍可能读取服务器文件、删库、写 WebShell。这不是假设,是真实攻击链。
- ✅ 实操:建专用账号,只赋
SELECT、INSERT、UPDATE、DELETE(按需),禁用GRANT、ALTER、EXECUTE - ❌ 常见错误:开发环境用 root 连接,上线忘记改;或者一个账号通吃所有业务库
- ? 检查命令:
SHOW GRANTS FOR 'app_user'@'%';,确认输出里没有高危权限项
错误信息绝不能暴露给前端
MySQL 默认错误如 Unknown column 'xxx' in 'where clause' 或 You have an error in your SQL syntax,会直接暴露表结构、字段名甚至 SQL 拼接逻辑。攻击者靠这个就能反推查询模板,绕过你自以为安全的“过滤”。
- ✅ 必做:Node.js 中捕获
connection.query()的 err,只返回通用提示(如“请求失败”),日志里记完整错误 - ❌ 避免:
res.json({ error: err.message })—— 这等于把数据库拓扑图送给攻击者 - ⚙️ 补充:MySQL 配置里设
sql_mode=STRICT_TRANS_TABLES,减少模糊错误带来的猜测空间
真正难的不是写对那行 ? 占位符,而是所有动态拼接点——包括日志上下文、缓存 key、SQL 注释里的调试字段——都得统一纳入输入审查流程。没人会检查你 console.log('querying table: ' + tableName) 这种地方,但它同样可能成为注入入口。











