标准答案

  1. 所有用户输入的值,包括 ID、搜索词、日期和分页游标,都应作为绑定参数传递,不能通过字符串拼接进入 SQL。
  2. 预编译或参数绑定能让数据库明确区分查询模板与参数值,输入中的引号、注释和关键字不会改变 SQL 语义。
  3. ORDER BY 的列名和 ASC 或 DESC 属于 SQL 结构,通常不能用普通占位符替代,应映射到固定允许列表。
  4. 动态筛选条件应由受控的条件构造器组合,字段名、操作符和排序都来自枚举,而不是直接信任请求参数。
  5. 参数化仍要配合最小数据库权限、错误信息控制和审计,不能把它当作唯一安全措施。

题目解析

许多注入漏洞不是发生在 WHERE name = ? 这种明显位置,而是排序、字段选择、批量导出或后台筛选功能。开发者以为“传的是排序字段不是值”而直接拼接,攻击者就能控制 SQL 结构。

白名单应映射业务 API 名称到真实列名。例如公开参数 sort=created 映射为 created_at,而不是允许调用方传任何数据库列,这也能避免内部字段被意外暴露。

代码示例

排序字段通过固定映射选择,用户输入只决定允许的业务选项。

TypeScript
const sortColumns = { newest: 'created_at', amount: 'amount' } as const
const column = sortColumns[input.sort as keyof typeof sortColumns] ?? 'created_at'
const direction = input.order === 'asc' ? 'ASC' : 'DESC'

const sql = `SELECT id, amount FROM orders WHERE tenant_id = ? ORDER BY ${column} ${direction}`
await db.query(sql, [tenantId])

常见误区

  • 只给字符串加引号或手动转义,仍让输入参与 SQL 结构拼接。
  • 认为 ORM 一定自动防注入,在 raw SQL、动态字段或模板查询中放松审查。

作者信息