flawopen.com/SQL Injection/数字 ID
为什么字符串格式化对数字 ID 也很危险?
参考页面
简答
风险与值在现实中的含义无关,而在于查询是通过拼接文本构造的。“它始终是数字”只是对数据的假设,并非代码强制保证。
假设失效的情形
HTTP 参数始终以字符串形式到达
URL 查询参数或表单字段在线路上传输时都是文本,与数据库列期望的类型无关。若不显式验证类型,框架不会在代码收到前阻止攻击者发送 id=1 OR 1=1 而不是数字。
后续重构可能悄悄改变值的来源
代码今天可能因 ID 始终来自可信内部来源而安全;如果新调用方把用户提供的值传给同一函数,代码就可能变得不安全。漏洞一直潜伏着,只等这项改动。
类型转换可能掩盖实际值
尤其在动态类型语言中,日志里看起来像数字的值仍可能是包含额外字符的字符串。没有显式验证,预期类型和实际类型并不保证一致。
即使验证了类型,也要对查询进行参数化
按照应用预期的格式验证 ID,然后通过数据库驱动的参数 API 传入。验证保护输入契约;参数化确保输入不会被解释为 SQL 语法。驱动在协议层的行为可能不同,因此不要假设所有驱动都以相同方式传输值。PostgreSQL 参数不能表示表名或列名;动态查询结构应从应用内固定的允许列表中选择。
绑定数值,并验证其业务含义
如果应用要求特定类型,请将 ID 验证为正的安全整数,同时仍将其作为查询值绑定。验证保护应用的输入契约;参数化则确保输入不会成为 SQL 语法。PostgreSQL 参数不能替代表名或列名,因此动态查询结构应从应用内固定的允许列表中选择。
不安全 — 将 ID 插入 SQL 文本
const rawId = new URL(request.url).searchParams.get("id");
const result = await pool.query(
`SELECT id FROM users WHERE id = ${rawId}`
);
安全 — 验证 ID 并将其作为值绑定
const rawId = new URL(request.url).searchParams.get("id");
if (rawId === null || !/^[0-9]+$/.test(rawId)) {
return new Response("Invalid id", { status: 400 });
}
const id = Number(rawId);
if (!Number.isSafeInteger(id) || id < 1) {
return new Response("Invalid id", { status: 400 });
}
const result = await pool.query(
"SELECT id FROM users WHERE id = $1",
Array.of(id)
);