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