如何在C#中实现SQL Prepare以防范SQL注入?
C#中防范SQL注入的实现方案(针对复杂WHERE子句场景)
核心原则
绝对禁止直接将用户输入或动态内容拼接成SQL字符串,所有变量值必须通过参数化查询处理;对于无法参数化的部分(如表名、列名),必须通过白名单校验限制范围。
具体实现步骤
1. 白名单校验动态表名、列名
表名、列名这类无法通过参数化处理的元素,必须预先定义允许访问的白名单,只有在白名单内的名称才能用于构建SQL:
- 定义允许的表名、列名集合
- 对传入的动态表名、选择列、IN子句的列进行校验,不在白名单内则直接抛出异常
2. 参数化处理复杂WHERE过滤子句
即使WHERE子句结构复杂(包含AND、OR、括号),所有变量值必须使用参数占位符(如@val1),不能直接拼接实际值:
- 预先构建带参数占位符的WHERE条件模板(如
(column2=@val2 OR column3=@val3 AND column4=@val4)) - 将对应的值通过
SqlParameter绑定到命令对象
3. 安全处理IN子句参数
IN子句的参数列表不能直接拼接字符串(如IN (1,2,3)),需为每个元素创建独立参数:
- 动态生成参数占位符列表(如
@inVal0,@inVal1,@inVal2) - 将每个IN元素绑定到对应的参数
完整代码示例
using System.Data.SqlClient; using System.Collections.Generic; using System.Linq; public void ExecuteSafeQuery(string selectColumns, string tableName, string whereConditionTemplate, List<SqlParameter> whereParams, string inColumn, IEnumerable<int> inValues) { // 定义允许访问的表和列白名单 var allowedTables = new HashSet<string> { "Users", "Orders", "Products" }; var allowedColumns = new HashSet<string> { "Id", "UserName", "Age", "OrderId", "ProductName" }; // 校验表名合法性 if (!allowedTables.Contains(tableName.Trim())) throw new ArgumentException("非法的表访问请求"); // 校验选择列合法性(简单拆分,复杂场景需解析SQL语法) foreach (var column in selectColumns.Split(',').Select(c => c.Trim())) { if (!allowedColumns.Contains(column)) throw new ArgumentException($"禁止查询列: {column}"); } // 校验IN子句的列合法性 if (!allowedColumns.Contains(inColumn.Trim())) throw new ArgumentException($"禁止使用的IN列: {inColumn}"); // 构建IN子句的参数占位符 var inParamNames = inValues.Select((_, index) => $"@inVal{index}").ToList(); string inClauseParams = string.Join(",", inParamNames); // 拼接最终SQL(仅拼接白名单校验后的表/列名、参数占位符) string finalSql = $"SELECT {selectColumns} FROM {tableName} WHERE {whereConditionTemplate} AND {inColumn} IN ({inClauseParams})"; using (var connection = new SqlConnection("Your_Connection_String")) { connection.Open(); using (var command = new SqlCommand(finalSql, connection)) { // 添加WHERE条件的参数 command.Parameters.AddRange(whereParams.ToArray()); // 添加IN子句的参数 for (int i = 0; i < inValues.Count(); i++) { command.Parameters.AddWithValue($"@inVal{i}", inValues.ElementAt(i)); } // 执行查询并处理结果 using (var reader = command.ExecuteReader()) { while (reader.Read()) { // 读取数据逻辑,例如: // int id = reader.GetInt32(reader.GetOrdinal("Id")); // string name = reader.GetString(reader.GetOrdinal("UserName")); } } } } }
关键注意事项
- 白名单校验要覆盖所有动态表/列名,避免遗漏;复杂场景下可借助SQL语法解析库确保列名格式合法
- 永远不要信任用户传入的任何SQL片段,即使声称仅包含AND/OR,也要确保所有变量值都通过参数传递
- 避免使用
EXEC或动态SQL执行方法,除非完全可控(如仅由内部代码生成,无用户输入参与) - 优先使用
SqlParameter的强类型方法(如Add指定SqlDbType)代替AddWithValue,避免隐式类型转换问题
内容的提问来源于stack exchange,提问作者RunXin Shirley
相关产品推荐
相关产品推荐

