提问人:sese sese 提问时间:6/7/2021 最后编辑:marc_ssese sese 更新时间:7/11/2021 访问量:3327
将表名作为参数传递给 SqlCommand 的问题
Issue to pass table name as parameter to a 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));
}
所以现在我们可以在原始代码块中使用它,请注意,表名和列名在这个脚本中已经转义了,这更多的是关于进入它的 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_SCHEMA
SYS.TABLES
我个人很少需要直接查询信息模式,但是它提供了一个与平台无关的解决方案,可以在需要时查询数据库结构。
评论
View
评论