階梯T-SQL:超越基礎等級9:動態T-SQL代碼
作者:Gregory Larsen,2016/07/29(第一次發布:2014/07/23)
原文鏈接:http://www.sqlservercentral.com/articles/Stairway+Series/113118/
系列
本文是階梯系列的一部分:T-SQL階梯:超越基礎
從他的階梯到T-SQL DML,Gregory Larsen涵蓋了更多的高級方面的T-SQL語言,如子查詢。
有時您需要編寫創建特定TSQL代碼的TSQL代碼並執行它。 執行此操作時,您將創建動態TSQL代碼。 用於創建動態TSQL的代碼可能很簡單,或者可能很復雜。 編寫動態TSQL時,您需要了解動態代碼如何打開SQL註入攻擊的可能性。 在本文中,我解釋了為什麽你可能想要使用動態
什麽是動態TSQL,為什麽要使用它?
什麽是動態TSQL? 動態TSQL是每次運行它時潛在的代碼。 它是一批在運行中生成和執行的TSQL代碼。 基於批處理中的某些條件或參數創建的即時生成代碼。 當“條件或參數”不同時,TSQL代碼會產生不同的TSQL以執行。
您希望以編程方式根據數據庫表中的參數和/或數據來確定所需的TSQL時,通常使用動態TSQL。 動態TSQL的用途是無止境的。 以下是您可能希望使用動態TSQL的兩個示例:
l 您希望用戶從下拉列表中選擇一些可能導致查詢運行不同的條件,例如
l 您的應用程序不知道要在運行時間之前運行的表的名稱。
因為TSQL語言不允許您使用變量或參數到特定的表或列名稱,因此可以使用動態TSQL。
為了更好地了解動態TSQL,我們來看幾個例子。
創建簡單的動態T SQL。
對於如何創建動態TSQL的第一個例子,我們來考慮以下情況。 假設您有一個應用程序,用戶界面允許用戶從下拉列表中選擇要讀取的表。 因此,每次有人使用界面時,他們都可以選擇一個不同的表,從中返回數據。 對於這個例子,我們假設這個用戶界面顯示了AdventureWorks2012數據庫中的表信息,用戶選擇了AdventureWorks2012.Sales.SalesOrderDetail
-- Declare variable to hold dynamic TSQL code
DECLARE @CMD nvarchar(1000);
-- Declare name of table to read
DECLARE @Table nvarchar(125);
SET @Table = ‘AdventureWorks2012.Sales.SalesOrderDetail‘;
-- Build dynamic TSQL Statement
SET @CMD = ‘SELECT TOP 10 * FROM ‘ + @Table;
--Execute dynamic TSQL Statement
EXECUTE (@CMD);
清單1:簡單動態SQL示例
清單1中的代碼首先聲明一個變量名稱@CMD來保存要構建的動態SELECT語句,並使用@Table變量來保存表名。 然後我將@Table變量設置為AdventureWorks.Sales.SalesOrderDetail。 要構建我實際的動態TSQL語句,我使用一個SET語句。 此語句將變量@CMD設置為包含SELECT語句和@TABLE變量值的級聯字符串值。 然後我使用EXECUTE語句執行@CMD變量中包含的動態TSQL語句。
為了進一步測試清單1中的動態TSQL,您可以通過更改“SET @ Table =”語句來嘗試在代碼中使用不同的AdventureWork2012表,以使用AdventureWorks2012.Sales.Sales.OrderHeader表。
處理更復雜的動態SQL Server要求。
有時你需要編寫一些更復雜的動態TSQL。 作為DBA,我可能需要這樣做的情況之一是當我想生成代碼來執行某種數據庫維護。 當我需要構建動態TSQL以進行數據庫維護時,通常會讀取系統視圖,然後生成顯示和/或執行的腳本。 假設您是已經接管了數據庫的DBA,並且您要刪除在數據庫中創建的多個測試表。 這些表都有以“Test”開頭的名稱。 為了演示如何讀取sys.tables視圖並生成相應的DELETE語句,我們來看看清單2中的代碼。
-- Section 1: Create database and Sample Tables
USE master;
go
CREATE DATABASE DYNA;
GO
USE DYNA;
GO
CREATE TABLE MyData1 (Id int, DataDesc varchar(100));
CREATE TABLE MyData2 (Id int, DataDesc varchar(100));
CREATE TABLE TestData1 (Id int, DataDesc varchar(100));
CREATE TABLE TestData2 (Id int, DataDesc varchar(100));
GO
-- Section 2: Dynamic TSQL code to generate script to delete Test tables
USE DYNA;
GO
DECLARE @TableName varchar(100);
DECLARE @CMD varchar(1000);
SELECT TOP 1 @TableName = name FROM sys.tables
WHERE name like ‘Test%‘
ORDER BY name;
WHILE @@ROWCOUNT > 0
BEGIN
SELECT @CMD = ‘DROP TABLE ‘ + @TableName + ‘;‘;
PRINT @CMD
EXECUTE(@CMD);
SELECT TOP 1 @TableName = name FROM sys.tables
WHERE name like ‘Test%‘ and name > @TableName
ORDER BY name;
END
-- Section 3: Cleanup
USE master;
GO
DROP DATABASE DYNA;
清單2:刪除測試表的動態代碼
清單2中的代碼包含三個不同的部分。第一部分創建一個名為DYNA的數據庫,然後創建4個不同的表,其中兩個表以“Test”開頭。以“Test”開頭的這兩個表是要用動態TSQL代碼刪除的表。代碼的第二部分是我的動態TSQL代碼。最後一部分代碼通過刪除我創建的測試數據庫進行清理。
如果您查看第2節中的代碼,您將發現動態TSQL代碼首先打印出運行的delete語句,然後刪除我在第1節中創建的測試表。我通過處理一個WHILE循環,同時尋找不同的表從字符串“Test”開頭。對於每個表,我發現以“Test”開頭,我構造了存儲在變量@CMD中的DELETE命令。然後通過使用PRINT語句顯示DELETE語句,然後立即使用EXECUTE語句執行語句。最後一節,第3節通過刪除DNYA數據庫進行清理。
為了測試這個代碼,我建議您從第1節開始,按照順序獨立運行每個部分。運行第1節後,查看DYNA數據庫並驗證DYNA數據庫中有4個表。 接下來運行第2節。運行此部分時,將在“查詢分析器”窗口的“消息”選項卡中看到兩條消息。 顯示的兩個語句是動態生成和執行的兩個DELETE語句。 一旦完成了第2節中的代碼,請返回並查看DYNA數據庫中的表。 如果您在SQL Server Management Studio中使用對象資源管理器,請勿忘記刷新。 或者,您可以從sys.tables視圖中進行選擇。 現在你應該會發現只有兩個表存在,而刪除的兩個表是那些以“Test”開頭的表。 一旦完成驗證第2部分中的代碼執行後,我將運行第3節中的代碼進行清理。 該代碼將刪除DYNA數據庫。
這是一個非常簡單的例子,說明如何檢查元數據行並生成動態TSQL。 作為DBA,有很多次會派上用場,以了解如何編寫生成TSQL代碼的TSQL代碼。
避免SQL註入。
你可能聽說動態TSQL是邪惡的。動態TSQL的惡性部分是開放SQL註入攻擊的可能性。 SQL註入是一種黑客技術,惡意用戶嘗試利用自由格式數據輸入字段。這些惡意用戶嘗試將額外的TSQL代碼插入數據輸入字段,超出了原始打算使用數據輸入字段的方式。通過插入TSQL代碼,他們可以愚弄系統返回原本不應該獲得的數據,或者更糟的是,對SQL Server數據庫運行附加的TSQL命令。根據您的應用程序運行的權限,SQL Injection攻擊可以將數據插入到數據庫表中,刪除表,或更糟糕的是,使用sysadmin權限設置新的登錄。
為了演示動態TSQL如果不能正確管理SQL註入攻擊,請先用清單3中的代碼創建一個數據庫和一個表。我將使用該數據庫和表來演示動態TSQL如何易受攻擊SQL註入攻擊。
USE master;
go
CREATE DATABASE DYNA;
GO
USE DYNA;
GO
CREATE TABLE Product(ID int,
ProductName varchar(100),
Price money);
INSERT INTO Product VALUES (1, ‘Red Wagon‘, 12.99),
(2, ‘Red Barn‘, 23.18),
(2, ‘Farm Animals‘, 7.59),
(2, ‘Toy Solders‘, 17.76);
清單3:創建數據庫和表以演示SQL註入攻擊
清單3中的代碼創建一個數據庫名稱DYNA,然後創建並填充具有4行數據的表名稱Product。
假設我的應用程序有一個數據選擇屏幕,最終用戶可以輸入一個包含在ProductName中的文本字符串,然後應用程序將返回包含輸入的文本字符串的所有Product表格記錄。 應用程序通過將用戶輸入的文本字符串傳遞到存儲過程名稱GetProducts,然後從存儲過程返回的數據顯示給用戶。 存儲過程GetProducts的編碼如清單4所示。
CREATE PROC GetProducts
(@EnteredText varchar (100))
AS
DECLARE @CMD varchar(1000);
SET @CMD = ‘SELECT ProductName, Price ‘ +
‘FROM Product ‘ +
‘WHERE ProductName LIKE ‘‘%‘ +
@EnteredText + ‘%‘‘‘;
PRINT @CMD
EXEC (@CMD);
清單4:存儲過程GetUserName的代碼
通過查看清單4中的存儲過程GetProducts,您可以看到此存儲過程接受單個參數@EnteredText此參數用於動態創建存儲在變量@CMD中的TSQL語句。 然後執行該變量。 (請註意,這個過程可能是在不使用動態SQL的情況下編寫的。我在這裏使用動態SQL來說明潛在的問題。)
為了演示如何使用這個存儲過程,我可以通過運行清單5中的代碼來執行它。
EXEC GetProducts ‘Red‘;
清單5正常執行存儲過程GetUserName。
清單5中的代碼調用GetProducts存儲過程,並生成報表1中的結果。
ProductName Price
------------------------------------------------------------------- -------------
Red Wagon 12.99
Red Barn 23.18
報告1:使用清單5中的代碼調用GetUserName的結果
因為我的存儲過程GetProducts中的代碼使用一個參數並生成varchar變量@CMD,因此存儲過程打開以進行SQL註入攻擊。 我可以通過使用清單6中的代碼執行GetProducts存儲過程來演示這一點。
EXEC GetProducts ‘Red%‘‘ and ID = 1 --‘;
清單6:顯示GetProducts存儲過程如何易受SQL註入的代碼
如果您查看清單6中的代碼,您可以看到我將一些其他字符附加到字符串“Red”到我的存儲過程GetProducts。我傳遞的這些附加字符允許我限制我的查詢,只返回ProductName列中具有“Red”的產品,ID值為1.通過允許我的存儲過程在@EnteredText參數中使用未編輯的文本,可以讓我在該參數中註入額外的字符,以使代碼執行其他最初未在GetProducts存儲過程中使用的操作。
在我的最後一個例子中,我使用myGetProducts存儲過程中的動態TSQL向您展示了非破壞性SQL註入攻擊。大多數SQL註入攻擊正在嘗試從系統中獲取額外的數據,或者只是想破壞您的數據庫。我們再來看一下清單7中的代碼。
EXEC GetProducts ‘Red‘‘ ;SELECT * FROM Product;--‘;
清單7:SQL註入以返回其他數據
如果我運行清單7中的代碼,它會生成兩個結果集。 第一個結果集具有零行,第二個集合是報表2中的文本:
ID ProductName Price
----------- ------------------------------------------------------------ ---------------------
1 Red Wagon 12.99
2 Red Barn 23.18
2 Farm Animals 7.59
2 Toy Solders 17.76
報告2:運行清單7中的代碼時的文本結果
如果比較結果1中找到的GetProduct存儲過程的正常執行結果與結果2中找到的結果,您可以看到清單7中的代碼生成了一些其他輸出列,我的存儲過程最初沒有設計為顯示,但是由於SQL註入攻擊而顯示。
清單7中的示例仍然不是對SQL Injection的破壞性使用,但它允許我利用GetProduct存儲過程的@EnteredText參數來返回Client表的所有列的數據。為了完成這個,我添加了“‘; SELECT * FROM Product; - ”字符串到我的參數。請註意,在我的附加字符串末尾添加了兩個破折號(“ - ”)。這允許我在參數後面註釋掉我的存儲過程可能包含的任何字符或代碼。
對於我的最後一個例子,讓我執行一個破壞性的TSQL註入攻擊。查看清單8中的代碼以查看我的破壞性TSQL註入命令。
EXEC GetProducts ‘Red‘‘ ;DROP TABLE Product;--‘;
清單8:破壞性TSQL註入EXEC命令
在清單8中,我向@EMAIL參數添加了一個DELETE語句。 在這個例子中,我刪除了客戶端表。 如果我運行清單8中的代碼,它將刪除Client表。
如何打擊SQL註入攻擊。
沒有人想要讓他們的代碼受到SQL註入攻擊的危害。 為了防止SQL Injection攻擊,您應該在開發TSQL應用程序代碼時考慮以下幾點:
l ·避免SQL Injection攻擊的最佳方法是不使用動態SQL
l ·編輯用戶輸入的特殊字符參數,如分號和註釋
l ·只有在需要支持用戶輸入的數據時,才能提供參數
l ·如果必須使用動態SQL,則使用參數化的TSQL,使用sp_execute sql來執行動態TSQL而不是EXEC。
l ·加強安全性,只允許執行動態TSQL所需的最少權限。
如果您的應用規範要求您需要構建一些包含動態TSQL的代碼,那麽使用參數化的TSQL是打擊SQL註入的好方法。 在清單9中,我提供了一個我如何修改我的GetUserName存儲過程以使用參數化的TSQL的例子。
ALTER PROC GetProducts
(@EnteredText varchar (100))
AS
DECLARE @CMD nvarchar(1000);
DECLARE @WildCardParm varchar(102);
SET @CMD = ‘SELECT ProductName, Price ‘ +
‘FROM Product ‘ +
‘WHERE ProductName LIKE @EnteredParm‘;
SET @WildCardParm = ‘%‘ + @EnteredText + ‘%‘;
EXEC sp_executesql @CMD,N‘@EnteredParm varchar(100)‘,@[email protected];
清單9:使用參數化的TSQL
在清單9中,我更改了我的GetProducts存儲過程,以使用sp_executesql來執行我的動態TSQL。 在這個修改後的存儲過程中,我做了以下更改:
l ·將字符串@CMD更改為不再包含命令字符串中@EnteredText變量的值。 而是將用戶輸入的文本引入名為@EnteredParm的變量中。
l ·添加一個SET語句,設置變量@WildCardParm將通配符(%)放在@EnteredText參數的開頭和結尾。
l ·更改如何執行字符串@CMD。 而不是使用EXEC語句來執行字符串,我使用過程sp_executesql。
通過進行這兩個更改,用戶輸入的文本現在將作為參數驅動查詢執行。 通過這樣做,用戶不能再嘗試在我的GetProduct存儲過程中註入額外的TSQL代碼。 要驗證這一點,請運行清單5,6,7和8所示的四個不同的命令。但是由於我已經刪除了我的產品表,所以我首先需要用數據重新創建它。 為此,我需要首先運行清單9中的代碼。
CREATE TABLE Product(ID int,
ProductName varchar(100),
Price money);
INSERT INTO Product VALUES (1, ‘Red Wagon‘, 12.99),
(2, ‘Red Barn‘, 23.18),
(2, ‘Farm Animals‘, 7.59),
(2, ‘Toy Solders‘, 17.76);
清單9:創建並填充Client表
在運行清單9以重新創建我的產品表之後,我可以運行列表5,6,7和8來證明我解決了我的SQL註入問題。 當您運行這些不同的命令時,您將發現只有清單5返回數據。 其他人不返回數據的原因是現在生成的動態TSQL正在尋找包含其他用戶輸入註釋值的ProductName值,當然這與“Product”表中的任何Product列值不匹配。
概要
沒有人想要對他們的手表進行SQL註入攻擊。 當然,確保不會發生的最佳解決方案是在您的應用程序中沒有動態SQL代碼。 如果您的應用程序確實需要動態SQL,那麽本文將為您提供一些有關如何最小化與SQL註入相關的風險的建議。 下次寫動態SQL時,請確保采取措施避免SQL註入攻擊的可能性。
問題和答案
在本節中,您可以通過回答下列問題來回顧您對SQL註入的了解程度。
問題1:
避免SQL註入攻擊的最佳方法是什麽(最好的方法)?
l 不部署使用動態TSQL的TSQL代碼
l ·編輯用戶輸入的動態TSQL中用於允許SQL註入攻擊的特殊字符的數據
l ·使動態TSQL的用戶輸入的參數盡可能短
l ·使用參數化的TSQL代碼
問題2:
用戶可以使用SQL註入附件來完成哪些事情(選擇所有適用的內容)?
l ·返回應用程序不希望用戶選擇的數據
l ·將數據插入到應用程序不打算使用的表中
l ·裁剪表格
l ·將系統管理員權限提供給新帳戶
l ·以上所有內容
問題3:
如果要部署變量中包含的動態TSQL代碼,最好使用這兩種執行方法中的哪一種來最大程度降低SQL註入攻擊的風險?
l ·EXEC
l ·sp_executesql
回答:
問題1:
正確的答案是 避免SQL註入的最佳方法是不允許您的應用程序中的動態TSQL代碼。
問題2:
正確的答案是e,以上所有。 使用SQL Injection,惡意用戶可以執行許多不同的SQL操作。 它們可以執行的命令類型取決於用於運行動態TSQL命令的帳戶的權限。 如果應用程序帳戶具有sysadmin權限,則SQL Injection攻擊可以執行用戶想要的任何操作。
問題3:
正確的答案是b。 通過使用sp_executesql,您可以傳遞用戶使用參數輸入數據到參數化的TSQL代碼。
本文是T-SQL階梯的一部分:超越基礎階梯
階梯T-SQL:超越基礎等級9:動態T-SQL代碼