QUOTENAME必须用于表名、列名等对象名,因为SQL Server不支持标识符参数化,仅支持值参数化;所有拼入动态SQL的对象名都需用QUOTENAME包裹以确保语法正确且防注入,而值参数必须通过sp_executesql的@params机制传递。

QUOTENAME 为什么必须用在表名、列名等对象名上
因为 SQL Server 的参数化查询(sp_executesql)只支持值(value)参数化,不支持标识符(identifier)参数化。你不能写 SELECT * FROM @table_name,SQL Server 会直接报错:Must declare the scalar variable "@table_name"。所以凡是拼进 SQL 字符串的表名、视图名、列名、数据库名、架构名,都得靠 QUOTENAME 包裹——它不是“可选加固”,而是语法刚需。
QUOTENAME 的正确调用方式和常见错误
QUOTENAME 接收最多两个参数:QUOTENAME(@input[, @quote_char])。第一个参数必须是 sysname 或 nvarchar(128) 类型,超长(>128 字符)直接返回 NULL;第二个参数指定引号字符,默认是方括号 [ ],也可用单引号 ' 或双引号 "。
常见错误包括:
- 对用户输入的列名列表不做拆解,直接拼进 SQL:比如
@col_list = N'username, email; DROP TABLE users --',再拼成'SELECT ' + @col_list + ' FROM ...'——QUOTENAME对整个字符串无效,必须逐个列名调用 - 用
REPLACE(@name, '''', '''''')手动转义单引号 —— 这无法防住含]的恶意输入(如orders]; DROP TABLE orders --),而QUOTENAME会自动转义内部的]为]] - 传入空字符串或
NULL:结果是NULL,导致最终 SQL 拼出SELECT * FROM NULL报错,需提前校验
动态生成视图或 SELECT 时怎么安全拼接多个对象名
当需要动态构造列名列表(如用户自定义字段)或跨库/跨架构引用时,不能把整段字符串交给 QUOTENAME,必须拆开、逐个处理:
✅ 正确做法:
DECLARE @table_name SYSNAME = N'orders'; DECLARE @col_list NVARCHAR(MAX) = N'customer_id, order_date, status'; DECLARE @quoted_cols NVARCHAR(MAX); <p>-- 拆列名、逐个 QUOTENAME、再用逗号拼接 SELECT @quoted_cols = STRING_AGG(QUOTENAME(value), N', ') FROM STRING_SPLIT(@col_list, N',');</p><p>DECLARE @sql NVARCHAR(MAX) = N'SELECT ' + @quoted_cols + N' FROM dbo.' + QUOTENAME(@table_name); EXEC sp_executesql @sql;
⚠️ 注意点:
-
STRING_SPLIT返回的value是varchar(8000),若列名含 Unicode 需显式转nvarchar - 如果列名来自不可信来源(如前端传入的 JSON 字段数组),必须先白名单过滤合法列名,再
QUOTENAME—— 否则攻击者可传status]; EXEC(''xp_cmdshell ''''whoami''''); --,虽被括起来但语义仍危险 -
QUOTENAME不处理空格开头/结尾,建议用TRIM()预清洗
QUOTENAME 在存储过程里该用在哪、不该用在哪
该用:所有拼进动态 SQL 的对象名变量,包括 @table_name、@schema_name、@db_name、@column_name(单个)。
不该用:值(value)参数——这些必须走 sp_executesql 的 @param 参数机制,而不是拼字符串。例如:
❌ 错误:
DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM Orders WHERE customer_id = ''' + @cust_id + ''''; EXEC sp_executesql @sql;
✅ 正确:
DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM ' + QUOTENAME(@table_name) + N' WHERE customer_id = @cid'; EXEC sp_executesql @sql, N'@cid INT', @cid = @cust_id;
混淆这两类场景是最常见的漏洞根源:以为用了 QUOTENAME 就万事大吉,结果值部分仍在拼接。

















