如何参数化 SQL 表而不遭受 SQL 注入漏洞

Nig*_*gel 2 c# sql sql-server sql-injection prepared-statement

我正在编写一个 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;
}
Run Code Online (Sandbox Code Playgroud)

上面的代码可以工作,但是它有一个明显的安全漏洞:SQL注入。

我的第一直觉是像这样参数化查询:

    using var command = new SqlCommand("SELECT TOP 0 * FROM @tableName", _connection);
    command.Parameters.AddWithValue("@tableName", tableName);
Run Code Online (Sandbox Code Playgroud)

但这会导致以下异常:

必须声明表变量“@tableName”

在 Stack Overflow 上快速搜索后,我发现了这个问题,它建议使用我的第一种方法(带有 sqli 漏洞的方法)。这根本没有帮助,所以我继续搜索并发现了这个问题,它说唯一安全的解决方案是对可能的表进行硬编码。同样,这不适用于我的类库,它需要适用于任意表名。

我的问题是:如何参数化表名而不受到 SQL 注入漏洞?

Aar*_*and 6

任意表名仍然必须存在,因此您可以首先检查它是否存在:

\n
IF EXISTS (SELECT 1 FROM sys.objects WHERE name = @TableName)\nBEGIN\n  ... do your thing ...\nEND\n
Run Code Online (Sandbox Code Playgroud)\n

此外,如果您希望允许用户从中选择的表列表是已知的且有限的,或者匹配特定的命名约定(例如dbo.Sales%),或者属于特定的模式(例如Reporting),您可以添加其他谓词来检查那些。

\n

这要求您将表名作为适当的参数传递,而不是连接或标记替换。(并且请永远不要用于AddWithValue()任何用途。)

\n

一旦您检查了对象的真实性和有效性,您仍然需要动态构建 SQL 查询,因为您仍然无法参数化表名。不过,您仍然应该申请QUOTENAME(),正如我在这些帖子中解释的那样:

\n\n

所以最终的代码会是这样的:

\n
CREATE PROCEDURE dbo.SelectFromAnywhere\n  @TableName sysname \nAS\nBEGIN\n  IF EXISTS (SELECT 1 FROM sys.objects\n      WHERE name = @TableName)\n  BEGIN\n    DECLARE @sql nvarchar(max) = N\'SELECT * \n      FROM \' + QUOTENAME(@TableName) + N\';\';\n    EXEC sys.sp_executesql @sql;\n  END\n  ELSE\n  BEGIN\n    PRINT \'Nice try, robot.\';\n  END\nEND\nGO\n
Run Code Online (Sandbox Code Playgroud)\n

如果您还希望它出现在某个定义的列表中,您可以添加

\n
AND @TableName IN (N\'t1\', N\'t2\', \xe2\x80\xa6)\n
Run Code Online (Sandbox Code Playgroud)\n

或者LIKE <some pattern>或者加入sys.schemas或者你有什么。

\n

如果没有人有权修改过程来更改检查,那么您就无法传递任何值来@TableName允许您执行任何恶意操作,除了可能从您不希望的另一个表中进行选择\xe2\x80\x99,因为有人在调用代码之前能够创建过多的访问权限。--替换诸如或 之类的字符;并不会让这变得更安全。

\n