sql server等数据库表名列名动态拼接必须用白名单或系统视图校验,禁用参数化;排序字段须case映射键值,sp_executesql需严格分离静态声明与动态拼接。

表名不能参数化,必须用白名单或系统视图校验
SQL Server、MySQL 等所有主流数据库都不支持 @table_name 这种写法,直接报错。拼表名是刚性需求,但也是注入高发区。靠 QUOTENAME() 或 REPLACE() 单独兜底无效——它只处理标识符语法,不拦 orders]; DROP TABLE logs-- 这类带分号的攻击。
安全路径只有两条,且必须二选一:
- 硬编码白名单:
IF @table_name NOT IN ('users', 'orders', 'products') THROW 50000, 'Invalid table', 1; - 查系统视图动态校验:
IF NOT EXISTS (SELECT 1 FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE s.name = 'dbo' AND t.name = @table_name)
注意:别用 OBJECT_ID(@table_name) 单独判断,它对恶意字符串截断后可能误判成功。
列名动态传参必须逐个 QUOTENAME + 白名单映射
列名和排序字段(如 @sort_col)同样无法参数化。常见错误是把用户传的逗号分隔字符串整段塞进 QUOTENAME(),比如 QUOTENAME('id, name; DROP TABLE x--') —— 这毫无意义,QUOTENAME 只作用于单个标识符。
正确做法是拆开、过滤、再包裹:
- 先用
STRING_SPLIT(@col_list, ',')拆成行 - 每行都走白名单检查:
WHERE value IN ('user_id', 'email', 'created_at') - 再用
QUOTENAME(value)逐个包裹,最后STRING_AGG(..., ', ')拼回
示例关键片段:SELECT @quoted_cols = STRING_AGG(QUOTENAME(value), N', ') FROM STRING_SPLIT(@col_list, N',') WHERE value IN ('id', 'name', 'status')
ORDER BY / GROUP BY 动态字段必须用 CASE 映射,禁用直拼
用户传 @order_by = 'name asc; DROP TABLE x',哪怕你加了 QUOTENAME(@order_by),结果是 [name asc; DROP TABLE x],SQL Server 仍会报语法错误,但攻击者已触发解析异常,可能暴露结构。
真正安全的做法是剥离语义,只接受可控键值:
- 定义映射规则:
CASE @order_by WHEN 'name' THEN 'user_name' WHEN 'id' THEN 'user_id' END - 排序方向单独处理:
CASE @dir WHEN 'desc' THEN 'DESC' ELSE 'ASC' END,再拼进 SQL 字符串(此时已是固定值) - 绝不允许
'ORDER BY ' + @order_by这类写法
映射表必须穷举所有合法组合,漏掉 user_email 就等于留了个后门。
sp_executesql 的参数声明和调用必须严格分离
很多人以为用了 sp_executesql 就万事大吉,结果在 @params 字符串里又拼接用户输入:N'@p nvarchar(50) = ''' + @user_input + '''' —— 这和 EXEC(@sql) 没区别,只是多绕了一层。
关键三要素必须同时满足:
-
@sql字符串里只出现占位符,如N'SELECT * FROM ' + QUOTENAME(@table_name) + N' WHERE status = @status' -
@params是静态字符串,显式声明类型:N'@status TINYINT',不能省略长度或用SQL_VARIANT - 执行时用命名绑定:
@status = @input_status,不能只写@input_status
最容易被忽略的是:动态 SQL 的安全性,从第一行 DECLARE @sql NVARCHAR(MAX) 就已决定。一旦开始拼值,防御就失效了。











