在C#中使用动态SQL的变量表名

时间:2012-12-25 00:45:02

标签: c# asp.net sql dynamic

我一直在寻找一种输入变量表名的方法,看起来最好的方法是使用动态sql,虽然它可以导致SQL注入。任何人都可以证明这是如何在C#中完成的吗?例如,我想要实现类似的东西:

 SqlCommand command= new SqlCommand("SELECT x FROM @table WHERE @column = @y", conn);

从中可以看出,表名和列名将是变量。我之前使用的是字符串连接,但出于安全考虑,我想避免这种情况。不确定它是否重要,但表和列不是由用户输入决定的,而是由用户选择的链接确定,所以SQL注入可能不是问题吗?

4 个答案:

答案 0 :(得分:2)

我能想到的最佳方式是两部分。

  1. 您有一个以名称作为参数的存储过程。这个proc首先查找目录中的表名(Information_Schema.Table)并获取正确的名称以验证它是否有效。

  2. 然后使用此查找存储过程构造所需的SQL而不是参数。

  3. 使用此查找,您基本上可以防止传入非有效的表名。

    CREATE PROCEDURE RunSQL
        (@table NVARCHAR(50))
    AS
    BEGIN 
        DECLARE @validTableName NVARCHAR(50)
    
        SELECT 
            @validTableName = TABLE_NAME    
        FROM 
            INFORMATION_SCHEMA.TABLES
        WHERE 
            TABLE_NAME = @table
            AND SCHEMA_NAME = 'dbo' -- here you can limit or customise the schema
    
        IF @validTableName IS NULL
            RAISE ... raise an exception
    
        DECLARE @dynamicSql NVARCHAR(100) = REPLACE('SELECT * FROM dbo.[#TABLE#]', '#TABLE' @validTableName) 
    
        EXECUTE sp_executesql .... @dynamicSql ....
    END
    

    您可以使用extend this来验证,架构,列等......

    您最终需要构建自己的SQL并执行针对注入的保护。没有绕过那个。

答案 1 :(得分:1)

似乎你必须使用动态SQL,这基本上意味着你必须将表名连接成一个查询字符串,并通过TSQL中的'sp_executesql'存储过程运行它。

使用SqlCommand不是动态SQL。虽然您正在动态构建SQL字符串,但您最后仍在运行一个普通的旧SQL字符串。

为了安全地执行此操作并针对SQL注入进行投影,您必须确保表名有效,并且必须通过任何必要的方式自行完成。幸运的是,你有3个不错的选择,其中一个很容易,几乎是万无一失的。

正如其他人所说,你可以:

  1. 根据白名单检查名称,或
  2. 在系统表中查找名称以查看它是否是真正的表名,或
  3. 只需使用QUOTENAME function转义字符并将其作为带引号的标识符。
  4. 我有一个关于按名称删除表的类似问题,这就是提到QUOTENAME函数的地方:https://stackoverflow.com/a/19528324/88409

    要使用动态SQL,您实际上必须执行以下操作:

    string sql = 
        "declare @query nvarchar(max) = 'SELECT * FROM ' + QUOTENAME(@tablename) + ' WHERE ' + QUOTENAME(@columnname) + ' = @cv'; " +
        "declare @params nvarchar(500) = N'@cv nvarchar(500)'; " +
        "exec sp_executesql @query, @params, @cv=@columnvalue;";
    
    SqlCommand command = new SqlCommand( sql, conn );
    command.Parameters.AddWithValue( "@tablename", tableName );
    command.Parameters.AddWithValue( "@columnname", columnName );
    command.Parameters.AddWithValue( "@columnvalue", columnValue );
    

    如果SqlCommand类不支持带有'declare'语句的复杂查询,那么你需要将'sql'字符串的值移动到一个存储过程中,该存储过程接受相同的三个参数然后通过将SqlCommand.CommandType设置为StoredProcedure来按名称调用它,如下所示:

    SqlCommand command = new SqlCommand( "MyStoredProcedureName", conn );
    command.CommandType = System.Data.CommandType.StoredProcedure;
    command.Parameters.AddWithValue( "@tablename", tableName );
    command.Parameters.AddWithValue( "@columnname", columnName );
    command.Parameters.AddWithValue( "@columnvalue", columnValue );
    

答案 2 :(得分:0)

最简单的方法是简单地使用字符串连接将表名放入SQL查询中。但是,只要恶意用户可以向查询中注入任意字符串,您仍然可以使用SQL注入。例如,假设您的API网址为http://example.org/all?table_name=customer&order=desc,您只是从用户名的网址中提取“table_name”。

您应该通过使用有效表名称的静态服务器端白名单(例如String[] ValidTableNames = new String[] { "customer", "purchase", ... })来防止可能的SQL注入,并确保table_name参数值是这些值之一,然后再将其添加到SQL查询中。

答案 3 :(得分:0)

我曾创建过一个允许某些用户访问特定表的网站。使用ASP成员资格和用户ID我有一个表,用于在创建用户帐户时建立USER ID之间的关系。当时已分配了允许访问的表名。表名是硬编码的,并根据项目的参数动态构建。因此,tblCustomersNew或tlbInventoryOld等表已链接到UserID。

当用户登录时,我只是针对SQL表查询UserID以获取表名,然后将其添加到查询中或将其传递给存储过程。

效果很好,所有这些都发生在经过身份验证的用户帐户后面。

编辑:我只想补充一点,这可以避免SQL注入,因为您的表名在项目中预定义并存储在数据库中。您只需使用经过身份验证的用户帐户查询它们即可访问用户有权访问的表。