必须使用参数化查询而非字符串拼接——前端校验可被绕过,JSON.stringify不防注入,parseInt等类型转换不能替代预编译,枚举字段需白名单+参数化,动态SQL部分须严格映射。

前端传来的任何数据,包括所谓“SQL条件”,都不应被直接拼接到查询中——这不是谨慎,而是底线。哪怕你加了前端校验、按钮禁用、请求拦截,攻击者只要打开 DevTools 改一行 fetch 就能绕过全部。
为什么 JSON.stringify 包裹的 SQL 条件仍是危险的
常见误区是:把用户选的筛选项(如 { status: 'active', category_id: 12 })转成 JSON 字符串,再让后端“解析后拼进 SQL”。这本质上只是换了一种方式传递原始输入,没解决根本问题。
- 攻击者可手动构造请求,把
category_id改成"12 OR 1=1 --",后端若用JSON.parse()再字符串拼接,照样触发注入 -
JSON.parse不校验语义,只保证格式合法;"true"、"null"、"SELECT * FROM users"都是合法 JSON 字符串 - 一旦服务端逻辑里出现
WHERE category_id = ${obj.category_id}这类模板拼接,类型转换就完全失效
parseInt 和 Number() 不能替代参数化查询
强制类型转换常被误当作“防注入手段”:比如对 ID 字段调用 parseInt(req.query.id),再塞进 SQL。它确实能拦住部分字符串型注入,但远不够用。
-
parseInt('123abc')返回123,看似安全,但若字段本应是字符串(如用户名),parseInt('admin\' OR 1=1--')会返回NaN,而很多代码没做isNaN判断就继续执行,反而引发逻辑错误或空值绕过 - 浮点数、科学计数法(
1e5)、负号(-1)等都可能被合法解析,但业务上未必允许 - 类型转换发生在拼接前,但拼接本身仍是硬编码 SQL —— 只要没进预编译流程,数据库仍会按 SQL 语法解析整条语句
真正有效的强制类型约束必须绑定到参数化查询上下文
类型转换只有和预编译语句结合才有意义。例如 Node.js 的 pg 库中,client.query('SELECT * FROM users WHERE id = ', [req.query.id]) 这个 占位符,才是类型安全的起点。
前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。
立即学习“前端免费学习笔记(深入)”;
- 你传入的
req.query.id会被驱动自动映射为 PostgreSQL 的integer类型(如果字段是 INT),超出范围或非法值会抛出明确错误,而不是静默截断或转换 - MySQL 的
mysql2中使用connection.execute('SELECT * FROM users WHERE status = ?', [req.query.status]),?占位符确保status被当作文本字面量,不会参与语法解析 - 关键点在于:类型判断由数据库驱动在协议层完成,不是靠 JS 的
parseInt或正则“猜”出来的
容易被忽略的边界:枚举字段和布尔条件
开发者常对数字 ID 做类型检查,却放行 status、sort_by、order 这类字符串字段,以为“只允许几个固定值”就够了。
- 写
if (['active', 'inactive'].includes(req.query.status))是必要但不充分的——若后续拼接时写成WHERE status = '${req.query.status}',攻击者只需把请求改成?status=active' --就能注释掉后面条件 - 更安全的做法是:先白名单校验,再通过参数化查询传入,且绝不拼接字段名或操作符(如
ORDER BY ${req.query.sort_by}是高危模式) - 涉及动态列名、表名、排序方向时,必须用严格白名单映射,例如
const sortMap = { created_at: 'created_at', updated_at: 'updated_at' },然后取sortMap[req.query.sort_by] || 'id',再拼进 SQL(此时已脱离用户输入控制)
最危险的不是不会写参数化查询,而是写了参数化查询,却在某个 if 分支里偷偷用字符串拼接补了一条“临时 SQL”。这类漏洞往往藏在分页逻辑、导出功能或管理后台的高级搜索里,上线后很久才暴露。

















