我正在撰寫一個 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 注入。
我的第一直覺是像這樣引數化查詢:
using var command = new SqlCommand("SELECT TOP 0 * FROM @tableName", _connection);
command.Parameters.AddWithValue("@tableName", tableName);
但這會導致以下例外:
必須宣告表變數“@tableName”
在對 Stack Overflow 進行快速搜索后,我發現了這個問題,它推薦使用我的第一種方法(具有 sqli 漏洞的方法)。這根本沒有幫助,所以我繼續搜索并找到了這個問題,它說唯一安全的解決方案是對可能的表進行硬編碼。同樣,這對我的類別庫不起作用,它需要為任意表名作業。
我的問題是:如何在沒有 SQL 注入漏洞的情況下引數化表名?
uj5u.com熱心網友回復:
任意表名仍然必須存在,因此您可以先檢查它是否存在:
IF EXISTS (SELECT 1 FROM sys.objects WHERE name = @TableName)
BEGIN
... do your thing ...
END
此外,如果您希望允許用戶從中進行選擇的表串列是已知且有限的,或者匹配特定命名約定(例如dbo.Sales%),或者屬于特定模式(例如Reporting),您可以添加額外的謂詞來檢查那些。
這要求您將表名作為適當的引數傳入,而不是連接或標記替換。(并且請AddWithValue()永遠不要用于任何事情。)
一旦您檢查物件是否真實有效,那么您仍然需要動態構建 SQL 查詢,因為您仍然無法引數化表名。QUOTENAME()不過,您仍然應該申請,正如我在這些帖子中所解釋的:
- 在 SQL Server 中保護自己免受 SQL 注入 - 第 1 部分
- 保護自己免受 SQL Server 中的 SQL 注入 - 第 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允許您執行任何惡意操作,除了可能從您不期望的另一個表中進行選擇,因為某人具有太多訪問權限能夠在呼叫代碼之前創建。替換像--或;這樣的字符不會使這更安全。
uj5u.com熱心網友回復:
您可以將表名傳遞給 SQL Server 以對其進行應用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 Server 上做動態部分,也使用quotename(). 但這似乎過于和不必要的乏味,特別是如果您將對不同位置的表進行多個操作。)
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/359849.html
標籤:C# sql sql-server 准备好的语句 sql注入
上一篇:確定每種產品的當前價格
