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

问题描述

这个问题的目标是避免 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));

解决方法

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

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

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

,

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

这是一个例子。

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 ...
}
,

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

这种清理方法向数据库添加了一个额外的请求,但比涉及存储过程的解决方案轻量级且更易于维护,因为它将数据查询逻辑保留在您的应用程序代码 (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));
}

所以现在我们可以在原始代码块中使用它,注意在这个脚本中表名和列名都被转义了,这比其他任何事情都更能适应它的习惯,特别是给定我们已经验证了表名:

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 指出 INFORMATION_SCHEMA 是比 MS SQL Server 特定的 SYS.TABLES 更好的解决方案。它是 ANSI 标准视图集,在 this Wikipedia page 中列出的许多 RDBMS 都支持该视图集。

我个人很少需要直接查询信息架构,但它提供了一种更平台无关的解决方案来在需要时查询数据库结构。