我正在编写一个 C# 类库,其中一个功能是能够创建与任何现有表的架构匹配的空数据表。
例如,这个:
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 注入(inject)。
我的第一直觉是像这样参数化查询:
using var command = new SqlCommand("SELECT TOP 0 * FROM @tableName", _connection);
command.Parameters.AddWithValue("@tableName", tableName);
但这会导致以下异常:
Must declare the table variable "@tableName"
在 Stack Overflow 上快速搜索后,我找到了 this question ,建议使用我的第一种方法(具有 sqli 漏洞的方法)。那根本没有帮助,所以我继续搜索并找到了 this question ,这表示唯一安全的解决方案是对可能的表进行硬编码。同样,这不适用于我的类库,它需要为任意表名工作。
我的问题是:如何参数化表名而不会受到 SQL 注入(inject)的影响?
最佳答案
任意表名仍然必须存在,因此您可以先检查它是否存在:
IF EXISTS (SELECT 1 FROM sys.objects WHERE name = @TableName)
BEGIN
... do your thing ...
END
此外,如果您希望允许用户从中选择的表列表是已知的和有限的,或者匹配特定的命名约定(如 dbo.Sales%
),或者属于特定的模式(如 Reporting
) ,您可以添加额外的谓词来检查这些谓词。
这需要您将表名作为适当的参数传递,而不是连接或标记替换。 (还有 please don't use AddWithValue()
for anything,永远。)
一旦检查对象真实有效,您仍然需要动态构建 SQL 查询,因为您仍然无法参数化表名。你还是应该申请QUOTENAME()
, 不过,正如我在这些帖子中解释的那样:
- Protecting Yourself from SQL Injection in SQL Server - Part 1
- Protecting Yourself from SQL Injection in SQL Server - Part 2
所以最终的代码应该是这样的:
CREATE PROCEDURE dbo.SelectFromAnywhere
@TableName sysname
AS
BEGIN
IF EXISTS (SELECT 1 FROM sys.objects
WHERE name = @TableName)
BEGIN
DECLARE @sql nvarchar(max) = N'SELECT *
FROM ' + QUOTENAME(@TableName) + N';';
EXEC sys.sp_executesql @sql;
END
ELSE
BEGIN
PRINT 'Nice try, robot.';
END
END
GO
如果您还希望它在某个定义的列表中,您可以添加
AND @TableName IN (N't1', N't2', …)
或LIKE <some pattern>
或加入sys.schemas
或者你有什么。
如果没有人有权修改程序以更改支票,则没有任何值可以传递给 @TableName
这将允许你做任何恶意的事情,除了可能从另一个你没想到的表中选择,因为有太多访问权限的人能够在调用代码之前创建。替换像 --
这样的字符或 ;
不会让这更安全。
关于c# - 如何参数化 SQL 表而不会受到 SQL 注入(inject)的影响,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70013463/