繁体   English   中英

如何参数化 SQL 表而不存在 SQL 注入漏洞

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM