SQL注入风险源于字符串拼接,参数化查询是唯一可靠防御手段;动态结构需白名单校验,AddWithValue存在类型推断缺陷,应显式声明参数类型。

字符串拼接 SQL 会直接触发 SQL 解析错误
SQL Server 的 SqlCommand 不做任何语法预检,它把 CommandText 当作原始指令原样发给数据库。一旦用户输入含单引号(如 O'Connor)、分号(; DROP TABLE)或注释符(--),拼出来的字符串立刻变成非法 SQL。常见报错:Incorrect syntax near ''' 或 Unclosed quotation mark——这不是程序 bug,是 SQL 引擎在拒绝执行你“亲手写坏”的语句。
所有用户输入都不可信,前端校验形同虚设
哪怕你在 JS 里用正则过滤了单引号,后端用 int.TryParse 判断了数字,攻击者仍可绕过:
- 用
CHAR(39)替代单引号(SQL Server 支持) - 用双写引号
''绕过简单替换逻辑 - 用 URL 编码、Unicode 等方式混淆检测规则
真正起作用的只有参数化机制——数据库驱动层在协议层面把值和结构分开传输,输入内容永远不会进入 SQL 解析器的词法分析阶段。
AddWithValue 不是银弹,显式类型声明才可靠
AddWithValue 看似省事,但类型推断常出错:
- 传空字符串
""→ 推成nvarchar(1),导致索引失效 - 传
null→ 推成Object,引发隐式转换失败 - 传
int→ 推成decimal,和字段类型不匹配时触发全表扫描
更稳的做法是显式指定 SQL 类型:cmd.Parameters.Add("@id", SqlDbType.Int).Value = userId。这不只是防注入,更是让查询计划稳定、性能可控的关键动作。
动态部分(表名、列名、排序字段)不能参数化,必须白名单控制
SQL Server 的参数化只支持值(WHERE name = @name),不支持结构(SELECT * FROM @table 会报错 Must declare the table variable "@table")。所以遇到动态表名、ORDER BY 字段等场景:
- 绝不能拼接用户输入
- 必须用预定义白名单比对:
if (!new[] { "Users", "Orders" }.Contains(tableName)) throw new ArgumentException(); - 存储过程里用
QUOTENAME()包裹(仅限可信上下文)
这点最容易被忽略:参数化只解决“值”的安全,不解决“结构”的安全。
真正危险的不是“会不会写 SQL”,而是误以为“拼得漂亮就安全”。只要有一处漏掉参数化,整条语句就回到裸奔状态。










