You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 07:19:59