C#中动态传递表名时如何避免SQL注入
动态切换SQL表名的安全方案
首先明确:SQL参数化查询不支持表名/列名这类数据库对象名的参数化,所以必须通过其他安全方式替代直接字符串拼接。针对你的场景,以下是几种可靠方案:
1. 白名单验证(最推荐,适配你的日期表场景)
你的表名是基于日期生成的(yyyyMMdd_HISTORY_DATA或yyyyMM_HISTORY_DATA),可以先验证生成的表名是否符合预期格式,同时查询数据库系统表确认表真实存在,双重保障避免注入风险:
优化后的C#代码
public DataSet Get_Data() { string connstr = ConfigurationManager.ConnectionStrings["DConn"].ConnectionString; string vTableName = null; if (DTP_DateFrom.Value == DTP_DateTo.Value) { vTableName = $"{DTP_DateFrom.Value:yyyyMMdd}_HISTORY_DATA"; } else { vTableName = $"{DTP_DateFrom.Value:yyyyMM}_HISTORY_DATA"; } // 第一步:格式校验,确保表名符合规则 var tableNameRegex = new System.Text.RegularExpressions.Regex(@"^\d{6,8}_HISTORY_DATA$"); if (!tableNameRegex.IsMatch(vTableName)) { MessageBox.Show("无效的表名格式", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return null; } DataSet ds = null; try { using (SqlConnection conn = new SqlConnection(connstr)) { conn.Open(); // 第二步:查询系统表确认表存在,避免注入或无效表名 string checkTableSql = @"SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = @TableName"; using (SqlCommand checkCmd = new SqlCommand(checkTableSql, conn)) { checkCmd.Parameters.AddWithValue("@TableName", vTableName); var exists = checkCmd.ExecuteScalar(); if (exists == null) { MessageBox.Show($"表{vTableName}不存在", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return null; } } // 第三步:安全拼接表名(已通过双重校验) string queryStr = $@"SELECT [ID] ,[type_1] ,[type_2] ,[type_3] ,[AMOUNT] ,[CR_DATETIME] FROM [DBO].[{vTableName}] WHERE CONVERT(DATE,CR_DATETIME) BETWEEN @P_DATE_FROM AND @P_DATE_TO AND UPPER(ID) LIKE UPPER(@P_ID)"; using (SqlCommand cmd = new SqlCommand(queryStr, conn)) { cmd.CommandTimeout = 15; cmd.Parameters.AddWithValue("@P_ID", "%" + TB_ID.Text + "%"); cmd.Parameters.AddWithValue("@P_DATE_FROM", DTP_DateFrom.Value); cmd.Parameters.AddWithValue("@P_DATE_TO", DTP_DateTo.Value); ds = new DataSet(); SqlDataAdapter da = new SqlDataAdapter(cmd); da.Fill(ds, "DGV_HISTORY_DATA"); // 绑定DataGridView列映射 DGV_DGV_HISTORY_DATA.Columns["ID"].DataPropertyName = "ID"; DGV_DGV_HISTORY_DATA.Columns["type1"].DataPropertyName = "type_1"; DGV_DGV_HISTORY_DATA.Columns["type2"].DataPropertyName = "type_2"; DGV_DGV_HISTORY_DATA.Columns["type3"].DataPropertyName = "type_3"; DGV_DGV_HISTORY_DATA.Columns["dgvAMOUNT"].DataPropertyName = "AMOUNT"; DGV_DGV_HISTORY_DATA.Columns["dgvCR_DATETIME"].DataPropertyName = "CR_DATETIME"; DGV_DGV_HISTORY_DATA.DataSource = ds.Tables["DGV_HISTORY_DATA"].DefaultView; L_Count.Text = DGV_DGV_HISTORY_DATA.RowCount.ToString("###,###"); } } } catch (Exception ex) { MessageBox.Show($"{ex.Message}\n GetDGV_HISTORY_DATA \n", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Error); return null; } return ds; }
2. 存储过程+动态SQL(适合复杂业务场景)
将表名校验和动态SQL逻辑放到数据库存储过程中,前端仅传递参数,进一步降低注入风险:
SQL Server存储过程示例
CREATE PROCEDURE GetHistoryData @TableName NVARCHAR(100), @DateFrom DATETIME, @DateTo DATETIME, @ID NVARCHAR(50) AS BEGIN SET NOCOUNT ON; -- 验证表名合法性 IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='dbo' AND TABLE_NAME=@TableName) BEGIN RAISERROR('无效的表名', 16, 1); RETURN; END -- 构造安全动态SQL,用QUOTENAME处理特殊字符 DECLARE @Sql NVARCHAR(MAX); SET @Sql = N' SELECT [ID],[type_1],[type_2],[type_3],[AMOUNT],[CR_DATETIME] FROM [dbo].' + QUOTENAME(@TableName) + N' WHERE CONVERT(DATE,CR_DATETIME) BETWEEN @P_DATE_FROM AND @P_DATE_TO AND UPPER(ID) LIKE UPPER(@P_ID)'; -- 参数化执行动态SQL EXEC sp_executesql @Sql, N'@P_DATE_FROM DATETIME, @P_DATE_TO DATETIME, @P_ID NVARCHAR(50)', @P_DATE_FROM = @DateFrom, @P_DATE_TO = @DateTo, @P_ID = @ID; END
C#调用存储过程的代码
public DataSet Get_Data() { string connstr = ConfigurationManager.ConnectionStrings["DConn"].ConnectionString; string vTableName = null; if (DTP_DateFrom.Value == DTP_DateTo.Value) { vTableName = $"{DTP_DateFrom.Value:yyyyMMdd}_HISTORY_DATA"; } else { vTableName = $"{DTP_DateFrom.Value:yyyyMM}_HISTORY_DATA"; } DataSet ds = null; try { using (SqlConnection conn = new SqlConnection(connstr)) { conn.Open(); using (SqlCommand cmd = new SqlCommand("GetHistoryData", conn)) { cmd.CommandType = CommandType.StoredProcedure; cmd.CommandTimeout = 15; cmd.Parameters.AddWithValue("@TableName", vTableName); cmd.Parameters.AddWithValue("@DateFrom", DTP_DateFrom.Value); cmd.Parameters.AddWithValue("@DateTo", DTP_DateTo.Value); cmd.Parameters.AddWithValue("@ID", "%" + TB_ID.Text + "%"); ds = new DataSet(); SqlDataAdapter da = new SqlDataAdapter(cmd); da.Fill(ds, "DGV_HISTORY_DATA"); // 绑定DataGridView列映射 DGV_DGV_HISTORY_DATA.Columns["ID"].DataPropertyName = "ID"; DGV_DGV_HISTORY_DATA.Columns["type1"].DataPropertyName = "type_1"; DGV_DGV_HISTORY_DATA.Columns["type2"].DataPropertyName = "type_2"; DGV_DGV_HISTORY_DATA.Columns["type3"].DataPropertyName = "type_3"; DGV_DGV_HISTORY_DATA.Columns["dgvAMOUNT"].DataPropertyName = "AMOUNT"; DGV_DGV_HISTORY_DATA.Columns["dgvCR_DATETIME"].DataPropertyName = "CR_DATETIME"; DGV_DGV_HISTORY_DATA.DataSource = ds.Tables["DGV_HISTORY_DATA"].DefaultView; L_Count.Text = DGV_DGV_HISTORY_DATA.RowCount.ToString("###,###"); } } } catch (Exception ex) { MessageBox.Show($"{ex.Message}\n GetDGV_HISTORY_DATA \n", "错误信息", MessageBoxButtons.OK, MessageBoxIcon.Error); return null; } return ds; }
关键注意事项
- 永远不要直接将用户输入(即使是日期控件生成的值)直接拼接进SQL,必须经过格式校验+系统表存在性检查。
- SQL Server的
QUOTENAME()函数可以自动处理表名中的特殊字符,避免语法错误和注入风险。 - 日期控件的值也需做格式校验,防止异常日期生成非法表名。
内容的提问来源于stack exchange,提问作者sam
相关产品推荐
相关产品推荐

