[英]How can I parameterize an SQL table without vulnerability to SQL injection
我正在编写一个 C# class 库,其中一个功能是能够创建一个与任何现有表的架构匹配的空数据表。
例如,这个:
private DataTable RetrieveEmptyDataTable(string tableName)
{
var table = new DataTable() { TableName = tableName };
using var command = new SqlCommand($"SELECT TOP 0 * FROM {tableName}", _connection);
using SqlDataAdapter dataAdapter = new SqlDataAdapter(command);
dataAdapter.Fill(table);
return table;
}
上面的代码有效,但它有一个明显的安全漏洞:SQL 注入。
我的第一直觉是像这样参数化查询:
using var command = new SqlCommand("SELECT TOP 0 * FROM @tableName", _connection);
command.Parameters.AddWithValue("@tableName", tableName);
但这会导致以下异常:
必须声明表变量“@tableName”
在对 Stack Overflow 进行快速搜索后,我发现了这个问题,建议使用我的第一种方法(带有 sqli 漏洞的方法)。 这根本没有帮助,所以我一直在搜索并找到了这个问题,它说唯一安全的解决方案是对可能的表进行硬编码。 同样,这不适用于我的 class 库,它需要适用于任意表名。
我的问题是:如何参数化表名而不会受到 SQL 注入的影响?
任意表名仍然必须存在,因此您可以先检查它是否存在:
IF EXISTS (SELECT 1 FROM sys.objects WHERE name = @TableName)
BEGIN
... do your thing ...
END
两者都要求您将表名作为适当的参数传递,而不是连接或令牌替换。 并且请永远不要将AddWithValue()
用于任何事情。
一旦您检查 object 是否真实通过,您仍然必须动态构建 SQL 查询,因为您仍然无法参数化表名。
有关保护自己免受 SQL 注入的更多详细信息,请参阅:
您可以将表名传递给 SQL 服务器以在其上应用quotename()
以正确引用它,然后仅使用带引号的名称。
类似于以下内容:
...
string quotedTableName = null;
using (SqlCommand command = new SqlCommand("SELECT quotename(@tablename);", connection))
{
SqlParameter parameter = command.Parameters.Add("@tablename", System.Data.SqlDbType.NVarChar, 128 /* nvarchar(128) is (currently) equivalent to sysname which doesn't seem to exist in SqlDbType */);
parameter.Value = tableName;
object buff = command.ExecuteScalar();
if (buff != DBNull.Value
&& buff != null /* theoretically not possible since a FROM-less SELECT always returns a row */)
{
quotedTableName = buff.ToString();
}
}
if (quotedTableName != null)
{
using (SqlCommand command = new SqlCommand($"SELECT TOP 0 FROM { quotedTableName };", connection))
{
...
}
}
...
(或者直接在 SQL 服务器上执行动态部分,也使用quotename()
。但这似乎过于繁琐且不必要,特别是如果您将在不同的地方对表执行多个操作。)
Aaron Bertrand 的回答解决了这个问题,但是存储过程对于可能与任何数据库交互的 class 库没有用处。 这是使用他的答案编写RetrieveEmptyDataTable
(我的问题中的方法)的方法:
private DataTable RetrieveEmptyDataTable(string tableName)
{
const string tableNameParameter = "@TableName";
var query =
" IF EXISTS (SELECT 1 FROM sys.objects\n" +
$" WHERE name = {tableNameParameter})\n" +
" BEGIN\n" +
" DECLARE @sql nvarchar(max) = N'SELECT TOP 0 * \n" +
$" FROM ' + QUOTENAME({tableNameParameter}) + N';';\n" +
" EXEC sys.sp_executesql @sql;\n" +
"END";
using var command = new SqlCommand(query, _connection);
command.Parameters.Add(tableNameParameter, SqlDbType.NVarChar).Value = tableName;
using SqlDataAdapter dataAdapter = new SqlDataAdapter(command);
var table = new DataTable() { TableName = tableName };
Connect();
dataAdapter.Fill(table);
Disconnect();
return table;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.