T-SQL進階:超越基礎 Level 9:動態T-SQL代碼

By Gregory Larsen, 2016/07/29 (首次發表於: 2014/07/23)sql

關於系列

本文屬於進階系列:Stairway to T-SQL: Beyond The Basics數據庫

跟隨Gregory Larsen的T-SQL DML進階系列,其涵蓋了更多的高級方面的T-SQL語言,如子查詢。編程


有時您須要編寫建立特定TSQL代碼的TSQL代碼並執行它。 執行此操做時,您將建立動態TSQL代碼。 用於建立動態TSQL的代碼可能很簡單,或者可能很複雜。 編寫動態TSQL時,您須要瞭解動態代碼如何打開SQL注入攻擊的可能性。 在本文中,我解釋了爲何你可能想要使用動態TSQL以及如何生成動態TSQL。 我還將探索SQL注入,並討論如何避免SQL注入攻擊您的動態TSQL代碼。安全

什麼是動態TSQL以及爲何你想要使用它?

什麼是動態TSQL?動態TSQL是每次運行它時潛在的代碼。它是一批在運行中生成和執行的TSQL代碼。基於批處理中的某些條件或參數建立的即時生成代碼。當「條件或參數」不一樣時,TSQL代碼會產生不一樣的TSQL來執行。sqlserver

您但願以編程方式根據數據庫表中的參數和/或數據來肯定所需的TSQL時,一般使用動態TSQL。動態TSQL的用途是無止境的。如下是您可能但願使用動態TSQL的兩個示例:測試

  • 您但願用戶從下拉列表中選擇一些可能致使查詢運行不一樣的條件,例如排序
  • 您的應用程序不知道在運行以前要運行的表的名稱

由於TSQL語言不容許您使用變量或參數到特定的表或列名稱,所以可使用動態TSQL。ui

爲了更好地瞭解動態TSQL,咱們來看幾個例子。編碼

建立簡單的T SQL

對於如何建立動態TSQL的第一個例子,咱們來考慮如下狀況。 假設您有一個應用程序,用戶界面容許用戶從下拉列表中選擇要讀取的表。 所以,每次有人使用界面時,他們均可以選擇一個不一樣的表,從中返回數據。 對於這個例子,咱們假設這個用戶界面顯示了DataBase AdventureWorks2012中的Table information,用戶選擇了Table AdventureWorks2012.Sales.SalesOrderDetail。 Listing 1中的代碼顯示了一種使用動態TSQL代碼從AdventureWorks.Sales.SalesOrderDetail表中返回TOP 10記錄的方法。設計

-- 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);
Listing 1:簡單動態TSQL示例

Listing 1中的代碼首先聲明一個變量名稱@CMD來保存要構建的動態SELECT語句,並使用@Table變量來保存表名。 而後我將@Table變量設置爲AdventureWorks.Sales.SalesOrderDetail。 要構建我實際的動態TSQL語句,我使用一個SET語句。 此語句將變量@CMD設置爲包含SELECT語句和@TABLE變量值的級聯字符串值。 而後我使用EXECUTE語句執行@CMD變量中包含的動態TSQL語句。code

爲了進一步測試Listing 1中的動態TSQL,您能夠嘗試經過修改「SET @ Table =」語句來在代碼中使用AdventureWork2012中不一樣的表,以使用AdventureWorks2012.Sales.Sales.OrderHeader表。

處理更復雜的動態SQL Server服務要求

有時你須要編寫一些更復雜的動態TSQL。 做爲DBA,我可能須要這樣作的狀況之一是當我想生成代碼來執行某種數據庫維護。 當我須要構建動態TSQL以進行數據庫維護時,一般會讀取系統視圖,而後生成顯示和/或執行的腳本。 假設您是已經接管了數據庫的DBA,而且您要刪除在數據庫中建立的多個測試表。 這些表都有以「Test」開頭的名稱。 爲了演示如何讀取sys.tables視圖並生成相應的DELETE語句,咱們來看看Listing 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;
Listing 2:刪除測試表的動態代碼

Listing 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注入式攻擊能夠將數據插入到數據庫表中,刪除表,或更糟糕的是,使用sysadmin權限設置新的登陸。

爲了演示動態TSQL若是不能正確管理SQL注入攻擊,請先用Lsting 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);
Listing 3: 建立數據庫和表來演示SQL注入式攻擊

Listing 3中的代碼將建立一個名爲DYNA的數據庫,而後建立並填充具備4行數據名爲Product的表。

假設個人應用程序有一個數據選擇屏幕,最終用戶能夠輸入一個包含在ProductName中的文本字符串,而後應用程序將返回包含輸入的文本字符串的全部Product表格記錄。 應用程序經過將用戶輸入的文本字符串傳遞到名爲GetProducts的存儲過程,而後將存儲過程返回的數據顯示給用戶。 存儲過程GetProducts的編碼如Listing 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);

經過查看Listing 4中的存儲過程GetProducts,您能夠看到此存儲過程接受單個參數@EnteredText,此參數用於動態建立存儲在變量@CMD中的TSQL語句。 而後執行該變量。 (請注意,這個過程多是在不使用動態SQL的狀況下編寫的。我在這裏使用動態SQL來講明潛在的問題。)

爲了演示如何使用這個存儲過程,我能夠經過運行清單5中的代碼來執行它。

EXEC GetProducts 'Red';
Listing 5:正常執行存儲在Procedure中的GetUserName

Listing 5中的代碼調用存儲在Procedure的GetUserName,並返回Report 1中的結果。

ProductName                                                         Price
------------------------------------------------------------------- -------------
Red Wagon                                                           12.99
Red Barn                                                            23.18

Report 1:使用Listing 5中的代碼調用GetUserName後的結果

由於個人存儲過程GetProducts中的代碼使用一個參數並生成varchar變量@CMD,所以存儲過程打開以進行SQL注入攻擊。 我能夠經過使用Listing 6中的代碼執行GetProducts存儲過程來演示這一點。

EXEC GetProducts 'Red%'' and ID = 1 --';
Listing 6:用於暴露GetProducts存儲過程是如何易受SQL注入的代碼

若是您查看Listing 6中的代碼,您能夠看到我將一些其餘字符附加到字符串「Red」後面到個人存儲過程GetProducts。 我傳遞的這些附加字符容許我限制個人查詢,只返回ProductName列中具備「Red」的產品,ID值爲1.經過容許個人存儲過程在@EnteredText參數中使用未編輯的文本,可讓我 在該參數中注入額外的字符,使代碼執行其餘最初未在GetProducts存儲過程當中使用的操做。

在個人最後一個例子中,我使用myGetProducts存儲過程當中的動態TSQL向您展現了非破壞性SQL注入攻擊。 大多數SQL注入攻擊正在嘗試從系統中獲取額外的數據,或者只是想破壞您的數據庫。 咱們再來看一下Listing 7中的代碼。

EXEC GetProducts 'Red'' ;SELECT * FROM Product;--';
Listing 7:SQL注入式攻擊返回額外的數據

若是我運行Listing 7中的代碼,它會生成兩個結果集。 第一個結果集具備零行,第二個集合是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
Report 2:執行Listing 7後的結果

若是比較Report 1中找到的GetProduct存儲過程的正常執行結果與Report 2中找到的結果,您能夠看到Listing 7中的代碼生成了一些其餘的輸出列,個人存儲過程最初並無設計爲顯示 ,但卻因爲SQL注入攻擊而顯示。

Listing 7中的示例仍然不是對SQL Injection的破壞性使用,但它容許我利用GetProduct存儲過程的@EnteredText參數來返回Client表的全部列的數據。 爲了完成這個,我添加了「'; SELECT * FROM Product; - 」字符串到個人參數。 請注意,在個人附加字符串末尾添加了兩個破折號(「 - 」)。 這容許我在參數後面註釋掉個人存儲過程可能包含的任何字符或代碼。

對於個人最後一個例子,我將執行一個破壞性的TSQL注入攻擊。 查看Listing 8中的代碼以查看個人破壞性TSQL注入命令。

EXEC GetProducts 'Red'' ;DROP TABLE Product;--';
Listing 8:破壞性的TSQL注入式攻擊EXEC命令

在Listing 8中,我向@EMAIL參數添加了一個DELETE語句。 在這個例子中,我刪除了客戶端表。 若是我運行Listing 8中的代碼,它將刪除Client表。

如何防止SQL注入式攻擊

沒有人想要讓他們的代碼受到SQL注入攻擊的危害。 爲了防止SQL 注入式攻擊,您應該在開發TSQL應用程序代碼時考慮如下幾點:

  • 避免SQL注入式攻擊的最佳方法是不使用動態SQL
  • 編輯用戶輸入的特殊字符參數,如分號和註釋
  • 僅在須要支持用戶輸入的數據時才能使參數發生
  • 若是必須使用動態SQL,則使用參數化的TSQL,使用sp_execute sql來執行動態TSQL而不是EXEC。
  • 增強安全性,只容許執行動態TSQL所需的最少權限。

若是您的應用規範要求您須要構建一些包含動態TSQL的代碼,那麼使用參數化的TSQL是防止SQL注入的好方法。 在Listing 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)',@EnteredParm=@WildCardParm;
Listing 9:使用參數化的TSQL

在Listing 9中,我更改了個人GetProducts存儲過程,以使用sp_executesql來執行個人動態TSQL。在這個修改後的存儲過程當中,我作了如下更改:

將字符串@CMD更改成再也不包含命令字符串中的@EnteredText變量的值。而是將用戶輸入的文本引入名爲@EnteredParm的變量中。
添加了一個SET語句,設置變量@WildCardParm將通配符(%)放在@EnteredText參數的開頭和結尾。
更改了字符串@CMD的執行方式。而不是使用EXEC語句來執行字符串,我使用過程sp_executesql。
經過進行這兩個更改,用戶輸入的文本如今將做爲參數驅動查詢執行。經過這樣作,用戶不能再嘗試在個人GetProduct存儲過程當中注入額外的TSQL代碼。要驗證這一點,請運行Listing 5,6,7和8所示的四個不一樣的命令。可是因爲我已經刪除了個人產品表,因此我首先須要用數據從新建立它。爲此,首先我須要運行Listing 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);
Listing 9:建立並填充Client表

在運行Listing 9從新建立個人產品表以後,我能夠運行Listing 5,6,7和8來證實我解決了個人SQL注入問題。 當您運行這些不一樣的命令時,您將發現只有Listing 5返回數據。 其餘人不返回數據的緣由是如今生成的動態TSQL正在尋找包含其餘用戶輸入註釋值的ProductName值,固然這與「Product」表中的任何Product列值不匹配。

總結

沒有人想要別人在他們眼皮底下進行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。經過使用sp_executesql,您能夠傳遞用戶使用參數輸入數據到參數化的TSQL代碼中。

相關文章
相關標籤/搜索