【问题标题】:Variable table name using dynamic SQL in C#在 C# 中使用动态 SQL 的变量表名
【发布时间】:2012-12-11 04:46:45
【问题描述】:

我一直在寻找输入变量表名的方法,似乎最好的方法是使用动态 sql,尽管它可能导致 SQL 注入。任何人都可以演示这是如何在 C# 中完成的吗?例如,我想要实现这样的东西:

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

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

【问题讨论】:

    标签: c# asp.net sql dynamic


    【解决方案1】:

    我能想到的最好的方法是两部分。

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

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

    使用这种查找,您基本上可以防止传入无效的表名。

    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
    

    您可以使用扩展它来验证、架构、列等...

    您最终需要构建自己的 SQL 并防止注入。没有办法解决这个问题。

    【讨论】:

      【解决方案2】:

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

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

      要安全地执行此操作并针对 SQL 注入进行项目,您必须确保表名有效,并且您必须通过任何必要的方式自行执行此操作。幸运的是,您有 3 个不错的选择,其中之一非常简单且几乎万无一失。

      正如其他人所提到的,您可以:

      1. 根据白名单检查名称,或
      2. 在系统表中查找名称以查看它是否是真实的表名,或者
      3. 只需使用QUOTENAME function 对字符进行转义并使其成为带引号的标识符。

      我有一个关于按名称删除表的类似问题,这就是提到 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 );
      

      【讨论】:

      • 嗨,我问这个问题已经有一段时间了,但我正在重新审视它。抱歉,老实说,我对 sql 注入很陌生。 QUOTENAME 是否将我的字符串从我的 sql 查询中的所有潜在坏字符中转义? (例如',',
      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

        我曾经创建了一个允许某些用户访问特定表的网站。使用 ASP Membership 和用户 ID,我有一个表,该表在创建用户帐户时建立了 USER ID 之间的关系。他们被允许访问的表名是当时分配的。表名是硬编码的,并根据项目的参数动态构建。所以 tblCustomersNew 或 tlbInventoryOld 等表被链接到 UserID。

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

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

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

        【讨论】:

          猜你喜欢
          • 2011-02-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多