商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > C#怎么防御SQL注入_C#防范万能密码与非参数化漏洞【避坑】

C#怎么防御SQL注入_C#防范万能密码与非参数化漏洞【避坑】

  发布于2026-05-23 阅读(0)

扫一扫,手机访问

C#怎么防御SQL注入_C#防范万能密码与非参数化漏洞【避坑】

C#怎么防御SQL注入_C#防范万能密码与非参数化漏洞【避坑】

开门见山,先说一个铁律:但凡用户输入要进SQL的地方,必须使用SqlParameter进行参数绑定。至于字符串拼接,无论是$""string.Format还是简单的+号,一律禁止在生产环境中间出现——这可不是什么“最佳实践建议”,而是关乎系统命脉的安全红线。

为什么万能密码 ' OR 1=1 -- 能绕过登录

来看一个典型的反面教材:"SELECT * FROM Users WHERE UserName = '" + txtUser.Text + "' AND Password = '" + txtPass.Text + "'"。当用户在用户名框输入admin'--时,拼接出来的SQL就变成了:SELECT * FROM Users WHERE UserName = 'admin'--' AND Password = ''。看到了吗?--后面的密码校验条件被直接注释掉了,攻击者就这样轻松登录了管理员账户。

这还不是最糟的。如果攻击者输入admin'; DROP TABLE Users; --,只要数据库账号拥有相应权限,整张用户表可能瞬间消失。问题的根源,从来不是前端验证不够严格,而是后端代码根本没有将数据与SQL指令分离开来。参数化查询的精髓,就在于让数据库引擎把@user这样的参数值当作纯粹的“数据”来处理。即便这个值是"; DROP TABLE Users; --,数据库也只会去寻找一个叫这个名字的用户,而不会将其中的分号识别为执行新命令的指令。

SqlCommand.Parameters.Add() 的三种写法怎么选

安全的核心,不在于“是否用了参数”,而在于“参数如何传递与设置”。常见的坑,往往隐藏在类型推断和空值处理这些细节里:

  • cmd.Parameters.AddWithValue("@name", userName):写法最简便,但也最危险。例如,传入一个null值,它可能被推断为SqlDbType.Int类型,导致后续类似WHERE status = @status的查询条件失效。传入短字符串时,也可能被推断为过小的VarChar(5),从而引发索引失效的性能问题。
  • cmd.Parameters.Add(new SqlParameter("@name", SqlDbType.NVarChar) { Value = userName }):这是推荐的做法。显式地指定参数类型(如SqlDbType.NVarChar)和长度(如Size = 50),可以完全避免隐式转换带来的不确定性,同时也能很好地处理null值(直接将Value赋值为(object)userName ?? DBNull.Value即可)。
  • cmd.Parameters.Add("@name", SqlDbType.NVarChar).Value = userName:这是一种语法糖,本质上与上一种写法相同。虽然可读性稍差,但只要类型指定明确,就没有实质性的安全风险。

动态条件和 IN 列表怎么安全写

这里是最容易“手滑”拼接SQL的重灾区。比如,面对一个带有“状态”、“部门”、“时间范围”等多个可选条件的组合查询,或者需要处理id IN (1,2,3...)这样的批量查询时,该怎么办?

对于动态WHERE条件,正确的思路不是拼接整个SQL字符串,而是拼接结构化的SQL片段,并为每个条件绑定固定的参数名:

var sql = "SELECT * FROM Orders WHERE 1=1";
var parameters = new List();

if (!string.IsNullOrEmpty(status)) { sql += " AND Status = @status"; parameters.Add(new SqlParameter("@status", SqlDbType.VarChar, 20) { Value = status }); }

if (startTime != null) { sql += " AND CreatedTime >= @startTime"; parameters.Add(new SqlParameter("@startTime", SqlDbType.DateTime2) { Value = startTime }); }

而对于IN列表,绝对不能写成"WHERE id IN (" + string.Join(",", ids) + ")"这种形式。因为SQL Server本身不支持直接将数组作为参数传入。正确的做法是:

  • 当ID数量不超过2000个时,可以通过循环动态生成@id1@id2…这样的占位符,然后为每个占位符调用Add()方法绑定具体的值。
  • 当ID数量超过限制时,则需要考虑使用DataTable结合SqlDbType.Structured(表值参数),或者先将ID列表写入临时表再进行关联查询。

存储过程和 EF Core 里那些“看起来安全”的陷阱

首先,存储过程并非SQL注入的“免检区”。常见的错误有两类:

  • 在C#层调用存储过程时,忘记设置cmd.CommandType = CommandType.StoredProcedure。结果SQL Server把"GetUserById"当作普通SQL语句执行,导致传入的参数全部被忽略。
  • 存储过程内部使用了EXEC(@sql)sp_executesql来动态拼接用户输入。这样一来,即便外部调用时使用了参数化,漏洞依然存在于存储过程内部,前功尽弃。

EF Core的情况也类似。使用FromSqlRaw("SELECT * FROM Users WHERE Name = {0}", name)是错误的,因为它本质上是字符串格式化。正确的写法是FromSqlRaw("SELECT * FROM Users WHERE Name = @name", new SqlParameter("@name", name))。另外,需要特别注意的是,像ORDER BY的字段名、表名、列名这类SQL结构部分,是无法参数化的。对于这些动态需求,必须采用白名单机制进行硬编码控制,例如:switch(sortBy) { case "name": order = "Name"; break; }

最后,必须牢记一个关键原则:参数化只保护“值”,不保护“结构”。如果你想通过传入@tableName参数来动态切换查询的表,SQL Server会直接报错。处理这类需求,只能依赖可信的白名单,或者在严格控制的上下文中使用QUOTENAME()函数,绝不能天真地试图用正则表达式过滤掉分号就以为万事大吉。

本文转载于:https://www.php.cn/faq/2410712.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注