sql server 不支持参数化表名或列名,必须用白名单校验后显式拼接;quotename 仅防单引号注入,无法抵御注释符等绕过手段,白名单是唯一可靠方案。

动态表名不能用 @param 参数化,这是 SQL Server 的硬限制
SQL Server 明确不支持把 @table 或 @column 当作标识符(如表名、列名、ORDER BY 字段)来参数化。你写 "SELECT * FROM @table" 会直接报错:Must declare the table variable "@table"。这不是 Dapper 的限制,是 T-SQL 语法层的死规则。所有“用参数代替表名”的尝试,本质都是在绕开数据库引擎的设计约束,必然失败。
白名单校验是唯一可靠方案,别信“QUOTENAME + 拼接”能兜底
有人用 QUOTENAME(@rawInput) 包一层再拼进 SQL,以为就安全了——其实不是。QUOTENAME 只防单引号闭合,但无法阻挡通过注释符(--)、换行符或 Unicode 形变绕过校验的攻击。真正可控的做法只有一种:提前定义合法值集合,运行时严格比对。
- 把允许的表名写死在配置或枚举里,比如
enum LogTable { Logs202601, Logs202602, Logs202603 } - 用户传入字符串后,先转成枚举或查白名单字典:
if (!validTables.Contains(userTable)) throw new ArgumentException("Invalid table name"); - 确认通过后再拼接:
string sql = $"SELECT * FROM [{userTable}] WHERE status = @status";
Dapper 中动态字段排序(ORDER BY)必须走白名单 + 显式拼接
像 "ORDER BY @sortField" 这种写法在 Dapper 里照样报错,而且 DynamicParameters 对它完全无效。Dapper 只能绑定值,不解析结构。常见错误是把排序字段也塞进匿名对象:new { sortField = "name" },结果 SQL 生成为 ORDER BY 'name'(带引号,变成字符串字面量,不是列名)。
正确做法是分离逻辑:
- 定义允许的排序字段:
var allowedSorts = new HashSet<string> { "created_at", "updated_at", "score" };</string> - 校验并拼接:
string orderBy = allowedSorts.Contains(sort) ? sort : "created_at"; - 最终 SQL:
string sql = $"SELECT * FROM logs ORDER BY {orderBy} DESC";
用 FormattableString 不等于自动防注入,它只保值不保结构
FormattableString 确实能帮 EF Core 实现安全插值,但它的保护范围仅限于「值」。一旦你用它拼表名:$"SELECT * FROM {tableName} WHERE id = @id",EF Core 仍会把 {tableName} 直接展开成字符串,不加引号也不校验——这和 $"" 拼接毫无区别,只是语法糖,不是防护机制。
关键判断点:看插值变量是否出现在 SQL 结构位置(FROM、ORDER BY、GROUP BY、AS 别名等)。只要出现,就必须人工白名单+显式拼接,没有捷径。很多团队踩坑就是因为误以为 “用了 FormattableString 就万事大吉”,结果上线后被扫出高危注入点。











