将表名作为参数传递给 SqlCommand 的问题

Issue to pass table name as parameter to a SqlCommand

提问人:sese sese 提问时间:6/7/2021 最后编辑:marc_ssese sese 更新时间:7/11/2021 访问量:3327

问:

这个问题的目标是避免SQL注入,我有查询(选择、插入、更新、删除)。

当我需要将表名作为参数发送时,我该怎么做?我将非常感谢您的帮助。

insertCommand.CommandText = "update @tableName set code = @code where rowid = @ID";
insertCommand.Parameters.Add(new SqlParameter("@tableName", table.Name));
insertCommand.Parameters.Add(new SqlParameter("@code", table.code));
insertCommand.Parameters.Add(new SqlParameter("@ID", table.id));
C asp.net C#-6.0

评论

1赞 shree.pat18 6/7/2021
你不能将表名作为参数传递,如果你想这样做,你需要使用动态 SQL。
8赞 Damien_The_Unbeliever 6/7/2021
通常,尝试参数化表名表明数据建模中犯了错误,而您应该拥有的是一个表,其中包含一个或多个附加列,其中包含当前已嵌入在表名中的数据
1赞 tozlu 6/7/2021
您可以构建一个包含动态表名的字符串。如果要防止注入,可以创建表名白名单,以检查动态表名参数。
0赞 sese sese 6/7/2021
你有什么例子可以做到这一点@tozlu吗?
0赞 tozlu 6/7/2021
@sesesese发布了带有示例的答案

答:

-1赞 Amit Kotha 6/7/2021 #1

您不能,因为表名必须在 SQL“编译时”可用。解决方法是使用动态查询方法。实际上,您将查询构建为字符串并在过程中执行它,因此您可以使用表名作为参数。

https://www.codeproject.com/Articles/20815/Building-Dynamic-SQL-In-a-Stored-Procedure

但是,我强烈建议您不要这样做,因为您最终会得到复杂且不可持续的应用程序

评论

0赞 Chris Schaller 7/11/2021
OP 特别担心 SQL 注入,并且已经在动态构造 SQL 语句,将该构造移动到存储过程中有助于参数化表名,但您已经说过这增加了额外的复杂性,因此它不是解决此特定问题的好方法。
2赞 tozlu 6/7/2021 #2

不能将参数用作表名。但是,如果要使用动态表名并防止注入。您可以创建表名白名单,并根据该列表检查动态参数。

下面是一个示例。

public void UpdateDynamicTable(string tableName, string code, string rowid){

  var listOfAllowedTableNames = new List<string>{
    "Vehicles", "Departments", "Companies"
  };
  if(!listOfAllowedTableNames.Contains(tableName){
    return; //You can return 400 (Bad Request) if its a web app
  }
  var updateCommand = ... //construct the command
  updateCommand.CommandText = $"UPDATE {tableName} SET code = @code WHERE rowid = @id";
  updateCommand.Parameters.Add(new SqlParameter("@code", code));
  updateCommand.Parameters.Add(new SqlParameter("@ID", rowid));
  // fire the update and return ...
}

评论

0赞 Flater 6/7/2021
此方法违反了 OCP。并不是说这不可逆转地排除了它作为一种可能的方法,但它也不是一个方法。
1赞 tozlu 6/7/2021
好吧,它不是用于严格注重原则的生产应用程序的代码,我也不负责提供这样的答案。这是为了向 OP 表明还有其他方法可以实现他们的要求。
2赞 Albert D. Kallal 6/8/2021
如果表名从不来自用户输入,那么我看不出这是任何问题。绝大多数 sql 命令对象都是自由格式的 sql - 事实上它使用参数 - 大多数代码都是这样工作的。我的意思是,对于应用程序中的每个sql命令,我认为它不需要为每种情况提供存储过程。只要用户输入的输入是参数,那么我不明白为什么这是一个问题?我的意思是 EF 或数据集不设置存储过程 - 他们只是将带有参数的 sql 提交到 sql 服务器,这就是这里发生的事情。
0赞 Chris Schaller 7/11/2021 #3

不能直接使用参数作为表名引用,但我们可以在执行查询之前根据当前数据库验证表名。

这种清理方法会向数据库添加额外的请求,但与涉及存储过程的解决方案相比,它更轻量级且更易于维护,因为它将数据查询逻辑保留在应用程序代码 (C#) 中,并且不需要以前将所有表列入白名单。

string chkTableSQL = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE [TABLE_NAME] = @table";
SqlCommand chkTable = new SqlCommand(chkTableSQL, conn);
chkTable.CommandType = CommandType.Text;
chkTable.Parameters.Add(new SqlParameter("@table", tableName));
var rows = (int?)chkTable.ExecuteScalar();

if(rows.GetValueOrDefault() == 0)
    throw new ArgumentException($"Table '{tableName}' not found.", nameof(tableName));

执行后,在构造的 SQL 命令中使用参数值应该是安全的。tableName

为了方便起见,您可以将上述代码打包到自己的方法中以供重用: 注意:
方法专门设计用于抛出异常而不是返回布尔状态,此模式可以更轻松地放入现有代码中,而无需创建新的 if 块,更改它以匹配您喜欢的编码范式

/// <summary>
/// Check the current database connection for the existence of the specified table
/// Throws an exception if the table does not exist.
/// </summary>
/// <param name="tableName">The name of the table to check against the specified <paramref name="conn"/></param>
/// <param name="conn">SQL Connection to query for the existence of the <paramref name="tableName"/></param>
/// <exception cref="ArgumentException">Throws exception when the <paramref name="tableName"/> was not found.</exception>
public void VerifyTableName(string tableName, System.Data.SqlClient.SqlConnection conn)
{
    string chkTableSQL = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE [TABLE_NAME] WHERE [name] = @table";
    var chkTableCommand = new System.Data.SqlClient.SqlCommand(chkTableSQL, conn);
    chkTableCommand.CommandType = System.Data.CommandType.Text;
    chkTableCommand.Parameters.Add(new System.Data.SqlClient.SqlParameter("@table", tableName));
    var rows = (int?)chkTableCommand.ExecuteScalar();

    if (rows.GetValueOrDefault() == 0)
        throw new ArgumentException($"Table '{tableName}' not found.", nameof(tableName));
}

所以现在我们可以在原始代码块中使用它,请注意,表名和列名在这个脚本中已经转义了,这更多的是关于进入它的 habbit 比其他任何事情都重要,特别是考虑到我们已经验证了表名:

VerifyTableName(table.Name, insertCommand.Connection);
insertCommand.CommandText = $"update [{table.Name}] set [code] = @code where [rowid] = @ID";
insertCommand.Parameters.Add(new SqlParameter("@tableName", table.Name));
insertCommand.Parameters.Add(new SqlParameter("@code", table.code));
insertCommand.Parameters.Add(new SqlParameter("@ID", table.id));

注意:出于各种原因,应避免涉及动态SQL生成或故意注入的解决方案,SQL注入攻击只是原因之一。通常有更好的设计、方法或ORM可以以不同的方式解决这类问题。

更新

感谢 Caius Jard 指出比 MS SQL Server 特定的解决方案更好。它是一组 ANSI 标准视图,在此维基百科页面中列出的许多 RDBMS 中都支持它。INFORMATION_SCHEMASYS.TABLES

我个人很少需要直接查询信息模式,但是它提供了一个与平台无关的解决方案,可以在需要时查询数据库结构。

评论

0赞 Caius Jard 7/11/2021
教授使用INFORMATION_SCHEMA将使这种技术更具跨数据库的可移植性
1赞 Chris Schaller 7/11/2021
谢谢@CaiusJard这是一个很好的建议,另一个更好的建议是名称也将包含在查找中,这可能对 OP 有用,在这种情况下,可以轻松引用视图View