必须用参数化查询,其他都是补救措施;?占位符仅用于值,表名列名须escapeid()转义或白名单校验;数据库账号需最小权限;错误信息严禁暴露;字符集必须统一为utf8mb4。

必须用参数化查询,其他都是补救措施。不这么做,光靠过滤、转义、WAF,迟早出事。
connection.query() 的 ? 占位符怎么用才安全
Node.js 里用 mysql2 或官方 mysql 包,connection.query() 支持 ? 占位符,这是唯一能保证值被安全处理的方式。它不是“推荐”,而是强制路径——所有用户输入进 SQL 的地方,都得走这条路。
- ✅ 正确:
connection.query('SELECT * FROM users WHERE id = ?', [userId], callback) - ❌ 危险:
connection.query('SELECT * FROM users WHERE id = ' + userId)(哪怕userId是数字,也别拼) -
?只保护值,不能用于表名、列名、ORDER BY字段——这些会直接报错或被忽略,不是 bug,是设计使然 - 多个参数按顺序填入数组,比如
WHERE status = ? AND created_at > ?对应[status, date]
动态表名和列名该怎么处理
当你不得不拼接标识符(比如按时间分表、多租户切换 schema),connection.escapeId() 是唯一靠谱的内置方法。它会把输入包裹在反引号里,并转义内部的反引号,防止 user`; DROP TABLE users; -- 这类 payload。
- ✅ 安全:
const sql = `SELECT * FROM ${connection.escapeId(tableName)}` - ❌ 无效:
connection.escape()不能用于表名——它只转义字符串值,对标识符无意义 - 更稳妥的做法是:提前定义允许的表名列表,运行时做白名单校验,比如
in_array(tableName, ['logs_202606', 'logs_202607'], true) - 字段排序(
ORDER BY)同理,禁止直接用req.query.sort,必须限定为['created_at', 'score', 'status']中的一个
为什么连上数据库还可能被拖库
即使代码全用参数化查询,一旦数据库账号有 DROP、CREATE、FILE 权限,攻击者拿到查询执行权后仍可能读取服务器文件、删库、写 WebShell。这不是假设,是真实攻击链。
- ❌ 常见错误:开发环境用
root连接,上线忘记改;或者一个账号通吃所有业务库 - ? 检查命令:
SHOW GRANTS FOR 'app_user'@'%',确认输出里没有DROP、CREATE、FILE、GRANT OPTION - 普通业务账号只该有
SELECT、INSERT、UPDATE、DELETE,且限制到具体库和表 - 错误信息绝不能暴露给前端:
Unknown column 'xxx' in 'where clause'这类提示等于把表结构送给攻击者
最易被忽略的一点:字符集不统一会让所有防护失效。即使开了预处理、关了模拟,如果连接字符集和 MySQL 服务端不一致(比如 PHP 连接用 utf8,服务端是 utf8mb4),宽字节注入仍可绕过。必须确保 DSN 带 ;charset=utf8mb4,且 character_set_client、character_set_connection、character_set_results 全部为 utf8mb4。











