1

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

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

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

4

4 回答 4

2

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

  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 并防止注入。没有办法解决这个问题。

于 2012-12-25T00:52:10.793 回答
1

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

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

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

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

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

我有一个关于按名称删除表的类似问题,这就是提到 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 );
于 2013-10-22T23:37:54.953 回答
0

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

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

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

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

于 2013-04-04T15:51:39.907 回答
0

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

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

于 2012-12-25T01:07:28.100 回答