[翻譯]通往T-SQL的樓梯
By Gregory Larsen, 2016/07/29 (first published: 2014/07/23)
通往T-SQL的臺階:超過基礎九級:動態T-SQL代碼
本文是系列的一部分
下面就從他通往的T-SQL DML,Gregory Larsen覆蓋了T-SQL語言更先進的方面例如子查詢。
有時候當你需要寫 T-SQL代碼,創建特定的代碼並且執行它。當你這樣做時你正在創建動態TSQL代碼。你用來創建動態TSQL可能是簡單的代碼,也可以是復雜的。當你寫動態TSQL時,你需要了解動態代碼怎樣打開一個SQL註入式攻擊的可能性。在這篇文章中,我解釋了當你可能想要使用動態的TSQL時如何動態生成它。我還將探討
什麽是動態的TSQL並且你為什麽想要使用它?
動態TSQL究竟是什麽?每次你運行動態TSQL時都可能是不同的代碼。基於批處理中的某些條件或參數創建了即時生成的代碼。基於批處理中的某些條件或參數創建了即時生成的代碼。當“條件或參數不同的TSQL代碼產生不同的TSQL來執行。
通常當你使用動態TSQL時,你要以編程的方式確定你需要基於數據庫中的表的參數或數據。動態TSQL的用途是無止境的。當你可能想要使用動態TSQL時,這裏有兩個例子:
1、您希望用戶從下拉列表中選擇一些可能導致查詢運行不同的標準,如排序順序。
2、應用程序不知道要運行的表的名稱,直到運行時為止。
動態TSQL可以用來代替由於TSQL語言不允許你使用變量或參數的特定表或列的名稱。
為了更好地了解動態TSQL讓我們看幾個例子。
創建簡單動態T SQL
對於第一個例子,如何創建動態TSQL讓我們考慮下面的情況。假設您有一個應用程序,其中用戶界面允許用戶從下拉列表中選擇要讀取的表。因此,每次有人使用接口時,他們都可以選擇一個不同的表,從中返回數據。在這個例子中我們假設從adventureworks2012數據庫顯示的這個用戶界面表的信息數據庫和用戶選擇的adventureworks2012.sales.salesorderdetail表。清單1中的代碼顯示了一個使用動態TSQL代碼返回的前
-- 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:簡單的動態TSQL實例
清單1中的代碼首先聲明一個變量名稱@ CMD來保存要構建的動態SELECT語句和@TABLE變量來保存表名。然後我設置“@TABLE變量來adventureworks.sales.salesorderdetail。為了建立我的實際動態TSQL語句我使用SET語句。此語句將變量CMD設置為包含SELECT語句和@TABLE變量值的級聯字符串值。然後我執行LE 包含在@CMD變量使用EXECUTE語句的動態TSQL語句。
為了進一步檢驗清單1中的動態TSQL,你可以通過改變“SET @Table=” statement to use the AdventureWorks2012.Sales.Sales.OrderHeader表嘗試使用不同的adventurework2012表代碼。
處理更復雜的動態SQLServer需求
還有當你需要寫一些更復雜的動態TSQL。作為DBA,我可能需要這樣做的一種情況是,當我想生成代碼來執行某種數據庫維護時。當我需要建立數據庫維護的目的,我通常看一個系統視圖和生成腳本,顯示和/或執行動態TSQL。假設您是一個DBA,接管了維護一個數據庫,並希望刪除在數據庫中創建的幾個測試表。表中都有以前綴“test”開頭的名稱。說明你可能讀sys.tables視圖並生成相應的delete語句,讓我們看看清單2中的代碼。
第一部分:創建數據庫和示例表
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
清單2:動態代碼刪除測試表
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個不同的表,然後從“測試”開始。這兩張表從“測試”開始,我想刪除動態TSQL代碼表。代碼的第二部分是我的動態TSQL代碼。最後一部分代碼通過刪除我創建的測試數據庫進行清理。
如果你查看第二部分的代碼你會發現動態TSQL代碼首先運行打印出DELETE語句,然後刪除我在1節中創建的測試表。我通過while循環處理,同時查找以字符串“test”開頭的不同表。對於每一個表,我發現以“test”開頭,我構建了一個刪除命令,該命令存儲在變量CMD中。然後,通過使用打印語句顯示刪除語句,然後緊接著通過使用執行語句來執行語句。最後一部分,第三部分清理刪除了DYNA數據庫。
為了測試此代碼,我建議您從第1節開始獨立運行每個部分。當您運行1節回顧動態數據庫和驗證在動態數據庫中有四張表。下一步運行第2節。當你運行完此部分時,您將看到兩個消息顯示在“查詢分析器”窗口中的“消息”選項卡中。顯示的兩個語句是動態生成和執行的兩個刪除語句。一旦你完成運行了第二部分的代碼,回去查看你的動態數據庫的表。如果您正在使用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註入攻擊可以將數據插入到數據庫表,刪除表,或者更糟,安裝一個新的登錄,系統管理員權限。
為了演示動態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中的代碼創建一個名D為YNA數據庫,然後用4行數據創建並填充一個名為生產的表。
假設我的應用程序有一個數據選擇屏幕,其中一個終端用戶可以輸入一個包含在ProductName中的文本字符串,然後應用程序將返回包含輸入的文本字符串的所有產品表記錄。應用程序通過傳遞用戶輸入到存儲過程名稱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:存儲過程返回用戶名密碼
通過查看清單4中的存儲過程get生成物,您可以看到這個存儲過程接受一個參數@enteredtext,然後使用這個參數動態地創建一個存儲在變量@cmd中的TSQL語句。然後執行該變量。(註意,這個過程可能是在不使用動態SQL的情況下編寫的。我在這裏使用動態SQL來說明潛在的問題。)
為了演示如何使用這個存儲過程,讓我通過運行清單5中的代碼來執行它。
EXEC GetProducts ‘Red‘;
清單5執行存儲過程的GetUserName
清單5中的代碼調用get生成物存儲過程,並生成報告1中所示的結果。
ProductName Price
------------------------------------------------------------------- -------------
Red Wagon 12.99
Red Barn 23.18
報告1:使用清單5中的代碼調用GetUserName的結果
因為在我的存儲過程的代碼getProducts需要一個參數,生成varchar變量@CMD它為SQL註入攻擊留下了存儲過程。我可以通過使用清單6中的代碼執行get生成物存儲過程來演示這一點。
EXEC GetProducts ‘Red%‘‘ and ID = 1 --‘;
清單6:用於公開get乘積存儲過程的代碼如何容易受到SQL註入的影響
如果您查看清單6中的代碼,您可以看到,我將許多其他字符傳遞給了我的存儲過程GetProducts的字符串“紅色”。我通過的這些附加字符允許我限制查詢,只返回產品名稱列中“紅色”的產品,並具有1的ID值。通過允許我的存儲過程在@enteredtext參數中使用未編輯的文本,允許我在該參數中註入額外的字符,從而使代碼執行其他原本不打算在get生成物存儲過程中使用的操作。
在我的最後一個例子中,我向您展示了在我的GetProducts存儲過程中使用動態TSQL的非破壞性SQL註入攻擊。大多數SQL註入攻擊都試圖從您的系統中獲取額外的數據,或者只是想破壞您的數據庫。為了進一步探索這個問題,我們來看看清單7中的代碼。
EXEC GetProducts ‘Red‘‘ ;SELECT * FROM Product;--‘;
Listing 7: SQL Injection to return additional data
If I run the code in Listing 7 it generates the two result sets. The first result set has zero rows and the second set is the text found in Report 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中的代碼時的文本結果
如果比較結果GetProduct正常執行的存儲過程中發現結果1,結果發現在結果2中,可以看到清單7中的代碼生成一些額外的輸出列,存儲過程並不是最初設計顯示,但顯示由於SQL註入攻擊。
清單7中的示例並不是SQL註入的破壞性使用,但它允許我利用GetProduct存儲過程的@enteredtext參數,為客戶表的所有列返回數據。為了完成這個任務,我添加了“‘ ;SELECT * FROM Product;--“ 字符串到我的參數。註意,我在附加字符串的末尾添加了兩個斜杠(“-”)。這允許我註釋掉我的存儲過程可能包含在參數之後的任何字符或代碼。
在我的最後一個例子中,讓我執行一個破壞性的TSQL註入攻擊。查看清單8中的代碼,以查看我的破壞性的TSQL註入命令
在清單8中,我向@email參數添加了一個DELETE語句。在本例中,我刪除了客戶機表。如果我運行清單8中的代碼,它將刪除客戶端表
如何對抗SQL註入攻擊
沒有人希望通過SQL註入攻擊來破壞他們的代碼。為了對抗SQL註入攻擊,您應該在開發您的TSQL應用程序代碼時考慮以下幾點:
1.避免SQL註入攻擊的最佳方法是不使用動態SQL
2.為特殊字符編輯用戶輸入參數,比如半冒號和註釋
3.只需要在需要的時候進行參數的輸入,以支持用戶輸入的數據
4.如果必須使用動態SQL,則使用參數化的TSQL,使用spexecute SQL來執行動態TSQL,而不是執行EXEC。
5.加強安全性,只允許執行動態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。在這個修改過的存儲過程中,我做了以下更改:
1.將字符串@cmd更改為在命令字符串中不再包含@enteredtext變量的值。相反,我在一個名為@enteredparm的變量中引入了用戶輸入的文本。
2.添加一個SET語句,設置變量@wildcardparm,在@enteredtext參數的開始和結束處放置通配符字符(%)。
3.更改了字符串@cmd的執行方式。我沒有使用EXEC語句來執行字符串,而是使用了spexecutesql。
通過對用戶輸入文本進行這兩個更改,現在將作為一個參數驅動的查詢執行。這樣,用戶就不能再嘗試在我的GetProduct store過程中註入額外的TSQL代碼了。要驗證這一點,請運行清單5、6、7和8中所示的4個不同命令。但是由於我已經刪除了我的Product表,所以我首先需要用數據重新創建它。為此,我需要首先運行清單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:創建和填充客戶端表
在運行清單9以重新創建我的產品表之後,我可以運行清單5、6、7和8,以證明我解決了SQL註入問題。當您運行這些不同的命令時,您會發現只有清單5返回數據。其他的不返回數據的原因是,現在生成的動態TSQL正在尋找包含附加用戶輸入值的產品名值,這當然不匹配產品表中的任何產品列值。
總結
沒有人希望對他們的表進行SQL註入攻擊。當然,確保它不會發生的最佳解決方案是在應用程序中沒有動態SQL代碼。如果您的應用程序確實需要動態SQL,希望本文提供了一些關於如何最小化與SQL註入相關的風險的建議。下一次編寫動態SQL時,一定要采取措施避免SQL註入攻擊的可能性。
問題和答案
在本節中,您可以通過回答以下問題來回顧您對SQL註入的理解程度。
問題1:
避免SQL註入攻擊的最佳方法是什麽(最好的方法)?
不要部署使用動態TSQL的TSQL代碼
編輯用戶輸入動態TSQL中使用的數據,用於支持SQL註入攻擊的特殊字符
讓用戶輸入盡可能短的動態TSQL的參數
使用參數化的TSQL代碼
問題2:
用戶可以通過SQL註入附加(選擇所有應用)來完成哪些事情?
返回應用程序不打算讓用戶選擇的數據
將數據插入到一個不是由應用程序設計的表中
?刪除一個表
為新帳戶提供系統管理員權限
上述所有的
問題3:
如果要部署包含在變量中的動態TSQL代碼,那麽這兩種執行方法中的哪一種是最好的,以便最大程度地減少SQL註入攻擊的風險?
- EXEC
- sp_executesql
回答:
問題1:
正確的答案是a.避免SQL註入的最好方法是在應用程序中不允許動態TSQL代碼。
問題2:
正確的答案是e,上面所有的。通過SQL註入,惡意用戶可以執行許多不同的SQL操作。它們可以執行的命令依賴於運行動態TSQL命令的帳戶的權限。如果應用程序帳戶擁有sysadmin權限,則SQL註入攻擊可以執行用戶想要的任何操作。
問題3:
正確的答案是b。通過使用spexecutesql,您可以將您的用戶輸入數據參數傳遞給參數化的TSQL代碼。
本文翻譯來源:http://www.sqlservercentral.com/articles/Stairway+Series/113118/
[翻譯]通往T-SQL的樓梯