在将表名动态传递给查询 C 时避免 SQL 注入#

Avoiding SQL Injection while dynamically passing table name to query C#

提问人:sam 提问时间:4/18/2023 最后编辑:Thom Asam 更新时间:4/18/2023 访问量:399

问:

我正在使用字符串连接将表名动态传递给命令字符串,但我现在这将导致 SQL 注入/

我试图将它作为参数传递,但它不起作用,所以我搜索了,但我没有得到答案。

有没有办法在不需要使用串联的情况下动态更改表名,这将导致我进入 SQL 注入案例?

    public DataSet Get_Data()
    {
        string connstr = ConfigurationManager.ConnectionStrings["DConn"].ConnectionString;
        string vTableName = null;

        if (DTP_DateFrom.Value == DTP_DateTo.Value)
        {
            vTableName = string.Concat(DTP_DateFrom.Value.ToString("yyyyMMdd"), "_HISTORY_DATA");
        }
        else if (DTP_DateFrom.Value != DTP_DateTo.Value)
        {
            vTableName = string.Concat(DTP_DateFrom.Value.ToString("yyyyMM"), "_HISTORY_DATA");
        }

        MessageBox.Show(vTableName);
        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)";

        try
        {
            SqlConnection conn = new SqlConnection(connstr);
            conn.Open();
            
            if (conn.State == ConnectionState.Open)
            {
                using (SqlCommand cmd = new SqlCommand(Querystr, conn))
                {
                    cmd.CommandTimeout = 15; 
                    cmd.CommandText = Querystr;
                    cmd.CommandType = CommandType.Text;

                    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);
                    //cmd.Parameters.AddWithValue("@P_TABLE_NAME", "'" + vTableName + "'");

                    ds = new DataSet();
                    SqlDataAdapter da = new SqlDataAdapter(cmd);
                    
                    da.Fill(ds, "DGV_HISTORY_DATA");

                    DGV_DGV_HISTORY_DATA.Columns["ID"].DataPropertyName = ds.Tables["DGV_HISTORY_DATA"].Columns["ID"].ColumnName;
                    DGV_DGV_HISTORY_DATA.Columns["type1"].DataPropertyName = ds.Tables["DGV_HISTORY_DATA"].Columns["type_1"].ColumnName;
                    DGV_DGV_HISTORY_DATA.Columns["type2"].DataPropertyName = ds.Tables["DGV_HISTORY_DATA"].Columns["type_2"].ColumnName;
                    DGV_DGV_HISTORY_DATA.Columns["type3"].DataPropertyName = ds.Tables["DGV_HISTORY_DATA"].Columns["type_3"].ColumnName;
                    DGV_DGV_HISTORY_DATA.Columns["dgvAMOUNT"].DataPropertyName = ds.Tables["DGV_HISTORY_DATA"].Columns["AMOUNT"].ColumnName;
                    DGV_DGV_HISTORY_DATA.Columns["dgvCR_DATETIME"].DataPropertyName = ds.Tables["DGV_HISTORY_DATA"].Columns["CR_DATETIME"].ColumnName;

                    DGV_DGV_HISTORY_DATA.DataSource = ds.Tables["DGV_HISTORY_DATA"].DefaultView;
                    L_Count.Text = String.Format(DGV_DGV_HISTORY_DATA.RowCount.ToString("###,###"));

                    return ds;
                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message + "\n GetDGV_HISTORY_DATA \n", "Error Message", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return null;
        }
        return null;
    }
C# SQL-Server SQL注入

评论

0赞 Thom A 4/18/2023
旁注:我们是否可以停止使用 AddWithValue(),这是邪恶的
2赞 TZHX 4/18/2023
您肯定有一个有限的表列表,其中包含要传入的这些列?检查在该列表中。vTableName
0赞 Thom A 4/18/2023
如果您打算从 C# 方面注入值,那么确保转义任何右括号 () 也很重要。仅仅将值括在括号 () 中并不能停止注入,因为恶意个人可以这样做。vTableName][]
2赞 Charlieface 4/18/2023
旁注:完全不可 sargeable(不能使用索引)。最好先截断日期,然后更改列排序规则,使其不区分大小写WHERE CONVERT(DATE,CR_DATETIME) BETWEEN @P_DATE_FROM AND @P_DATE_TO AND UPPER(ID) LIKE UPPER(@P_ID)WHERE CR_DATETIME BETWEEN @P_DATE_FROM AND @P_DATE_TO AND ID LIKE @P_IDID
1赞 Charlieface 4/20/2023
因此,您只需要发送已经截断的参数即可。例如,使用 和WHERE CR_DATETIME >= @P_DATE_FROM AND CR_DATETIME < @P_DATE_TO@P_DATE_FROM='20230201'@P_DATE_TO='20230202'

答:

2赞 Zohar Peled 4/18/2023 #1

在使用动态 SQL 时,保护自己免受 SQL 注入影响的方法非常简单:尽可能使用参数,并将任何无法参数化的内容列入白名单。

由于标识符无法在 SQL 中参数化,因此在使用它们之前,必须将其列入白名单。在这种情况下,请确保它实际上包含查询中包含列的表的名称。vTableName

话虽如此,您拥有这样的动态查询这一事实会引发有关数据库设计的问题 - 具有相同列列表的多个表通常强烈表明数据库设计不佳。

评论

0赞 sam 4/20/2023
感谢您的回答,但对于您说的最后一点,一个表格包含的表格每小时插入的记录都比周围多,当我进行查询时,它需要大约..所以我每天创建一个表格,空原来的桌子......我尝试使用索引,但它们根本没有用......请问对这种情况有什么建议吗?bad database design.13 million records500,0004 minutes or more
0赞 Zohar Peled 4/20/2023
可能有更好的选择,如分区和过滤索引,但要提供适合您需求且可扩展的解决方案,您需要咨询能够访问您的实际数据库并可以验证实际需求的 DBA。这远远超出了你从互联网上一些随机的陌生人(如我)那里得到的 Stackoverflow 答案。我们在这里谈论的是专业帮助,这将花费您一些钱。
0赞 Neil 4/18/2023 #2

在使用前验证输入,可以防止攻击媒介:

    if (DTP_DateFrom.Value == DTP_DateTo.Value)
    {
        if(!DateTime.TryParseExact(DTP_DateFrom.Value, "yyyyMMdd", out var _))
            throw new ArgumentException();

        vTableName = string.Concat(DTP_DateFrom.Value.ToString("yyyyMMdd"), "_HISTORY_DATA");
    }
    else if (DTP_DateFrom.Value != DTP_DateTo.Value)
    {
        if(!DateTime.TryParseExact(DTP_DateFrom.Value, "yyyyMM", out var _))
            throw new ArgumentException();

        vTableName = string.Concat(DTP_DateFrom.Value.ToString("yyyyMM"), "_HISTORY_DATA");
    }

评论

1赞 Zohar Peled 4/18/2023
如果是字符串,我会感到非常惊讶。问题中的 c# 代码指示它是 (或 )。DTP_DateFrom.ValueDateTimeDateTimeOffset
0赞 Neil 4/18/2023
@ZoharPeled是的,我也是。我在意识到之前发布了这个。
0赞 sam 4/20/2023
@Neil 方法“TryParseExact”没有重载需要 3 个参数
1赞 Neil 4/20/2023
我要删除这个答案,你已经有一个 DateTime,所以没有必要将其转换为字符串,只需再次解析它。正如我在 OP 上评论的那样,您没有 SQL 注入攻击向量。