簡體   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