身份验证绕过
admin' OR '1'='1通过使 WHERE 条件恒为真来尝试绕过登录
用户提供的值不会被拼接进 SQL 文本——通过参数占位符绑定(随方言不同,例如 $1 或 ?)
SQL 注入仍然是最关键的 Web 应用漏洞之一
当不受信任的数据未经安全参数绑定就被并入 SQL 文本时,就会发生 SQL 注入。攻击者可能改变查询语义,从而访问未授权数据、修改记录或触发非预期操作。
admin' OR '1'='1通过使 WHERE 条件恒为真来尝试绕过登录
' UNION SELECT password FROM users--通过 UNION 尝试从其他表提取数据
'; DROP TABLE users; --尝试追加额外语句以删除数据
利用:如果占位符与参数错配,权限检查可能使用错误的值示例:WHERE userId=$1 AND isAdmin=$2,但 params=[true, 123] 而不是 [123, true],可能错误授予管理员权限
每个查询都会经过多层安全校验
所有用户提供的值都会转换为绑定参数。值不会以字面量形式进入 SQL 文本。
在生成查询之前,所有字段名都会根据 Prisma schema 元数据校验。
表名、列名与别名会被校验,并在需要时安全地加引号/转义。
每种字段类型仅允许已知的安全操作符。
严格的顺序确保每个占位符按插入顺序恰好映射到一个参数,防止错配。
在明确设计前提下的抗注入证明概要(参数绑定、无 raw-SQL 绕过、驱动正确使用)
对于所有用户提供的值 V,不存在执行路径使 V 作为 SQL 文本出现在生成的查询字符串中。
按构造,所有处理用户值的代码路径都会调用 addParameter(value),它: 1. 将值存储在独立的 params 数组中 2. 返回用于 SQL 文本的占位符 token 3. 只有占位符 token 会拼入 SQL 字符串 4. 数据库驱动分别接收 SQL 文本与参数值 ∴ 用户值不会被当作 SQL 语法解析
const addParameter = (params: any[], value: any) => {
params.push(value)
const index = params.length
return `$${index}`
}任何生成查询中的字段名集合 F 都是 schema 定义字段集合 S 的子集,即 F ⊆ S。
对每一次字段引用: 1. 从查询对象中提取字段名 2. 按 schema 元数据查找 3. 若字段不在 schema 中则抛错 4. 只有通过校验的字段才能进入 SQL 生成 ∴ 任意字段名不能出现在 SQL 中
const validateField = (field: string, model: Model) => {
if (!model.fields.has(field)) {
throw new Error(`Field ${field} does not exist`)
}
return model.fields.get(field)
}对字段 F 应用的任意操作符 O,都必须属于字段类型 T 允许的操作符集合。
操作符校验算法: 1. 从 schema 获取字段类型 T 2. 定义 allowed_ops(T) = { 类型 T 允许的操作符 } 3. 对查询中的操作符 O: - 若 O ∉ allowed_ops(T),抛错 - 否则以参数绑定方式应用操作符 ∴ 只能使用类型匹配的操作符
const ALLOWED_OPS: Record<string, string[]> = {
String: ['contains', 'startsWith', 'endsWith'],
Int: ['lt', 'lte', 'gt', 'gte']
}
if (!ALLOWED_OPS[fieldType]?.includes(operator)) {
throw new Error('Invalid operator')
}所有 SQL 标识符 I 都会校验以排除控制字符,并按方言规则进行安全转义与加引号。
标识符处理: 1. 若包含控制字符则拒绝 2. 按方言规则转义内部引号 3. 在需要时加引号(保留关键字或包含特殊字符) 4. 按方言规则应用标识符长度限制 ∴ 标识符不能破坏 SQL 语法或注入 token
const quoteIdentifier = (id: string) => {
if (/[\x00-\x1F]/.test(id)) {
throw new Error('Invalid characters')
}
const escaped = id.replace(/"/g, '""')
const needsQuoting = /[^a-z0-9_]/i.test(id) || isReservedKeyword(id)
return needsQuoting ? `"${escaped}"` : id
}对 SQL 文本中出现的每个占位符位置 N,params[N-1] 都是该位置预期的确切值,不会发生重排或错配。
参数顺序保证: 1. 单一追踪器维护插入顺序 2. 每次 add() 调用都会将值追加到 params,并立即返回下一个占位符 token 3. SQL 文本构建与 params 构建同步进行 4. 不存在中间重排步骤 ∴ 一对一映射始终成立
class ParameterTracker {
private params: any[] = []
add(value: any): string {
this.params.push(value)
return `$${this.params.length}`
}
getParams(): any[] {
return this.params
}
}
const tracker = new ParameterTracker()
const sql = `WHERE email = ${tracker.add(email)} AND age > ${tracker.add(age)}`
const params = tracker.getParams()当前套件验证对常见 SQL 注入向量与边缘情况的防护
const email = req.body.email
const sql = "SELECT * FROM users WHERE email = '" + email + "'"const email = req.body.email
const { sql, params } = toSQL('User', 'findMany', {
where: { email }
})
const result = { sql, params }集中式参数跟踪确保每个用户提供的值都经过参数绑定且顺序稳定
class ParameterTracker {
private params: any[] = []
add(value: any): string {
this.params.push(value)
return `$${this.params.length}`
}
getParams(): any[] {
return this.params
}
}基于 schema 的验证防止任意字段访问
function validateField(
fieldName: string,
model: ModelInfo
): FieldInfo {
const field = model.fields.get(fieldName)
if (!field) {
throw new Error(
`Field "${fieldName}" does not exist`
)
}
return field
}方言感知的标识符校验与加引号可防止通过标识符触发的语法注入
function quoteIdentifier(identifier: string): string {
if (/[\x00-\x1F]/.test(identifier)) {
throw new Error('Invalid control characters')
}
const escaped = identifier.replace(/"/g, '""')
const needsQuoting = /[^a-z0-9_]/i.test(identifier) || isReservedKeyword(identifier)
if (needsQuoting) {
return `"${escaped}"`
}
return identifier
}不要绕过 schema 验证或字段检查。这些是关键的安全层。
确保你的 Prisma schema 准确反映数据库结构。
TypeScript 提供编译时安全性,但运行时验证可提供更深一层防护。
在生产中记录并监控生成的 SQL 与参数,以检测异常与误用。