樓梯到T-SQL:超越基礎水平9:動態T-SQL代碼

樓梯到T-SQL:超越基礎水平9:動態T-SQL代碼sql

  做者:Gregory Larsen,2016/07/29(第一次發佈:2014/07/23)數據庫

本文是樓梯系列的一部分:T-SQL樓梯:超越基礎編程

接下來從他的樓梯到T-SQL DML,Gregory Larsen涵蓋了更高級的T-SQL語言,如子查詢。安全

什麼是動態TSQL,爲何要使用它?服務器

動態的TSQL是什麼?動態TSQL是一種每次你運行它均可能生成並執行生成不一樣的TSQL代碼,生成的代碼是基於某些條件來建立的。在批處理參數中,當「條件或參數」不一樣時,TSQL代碼會生成不一樣的TSQL來執行。你一般使用動態TSQL,當你但願以編程方式肯定TSQL字數據庫表中所須要的,或參數和/或數據時,動態TSQL的用途是無窮無盡的。下面是你可能想要使用動態TSQL的兩個示例:測試

一、你想要用管理員身份去從下拉列表中選擇一些標準,這可能會使查詢的順序不一樣於排列順序。ui

二、你的應用程序不知道要運行的這個表的名稱。編碼

 

因爲TSQL語言不容許你使用變量或參數特定的表或列名,所以可使用動態TSQL.設計

 

爲了更好地理解動態tsql,讓咱們來看幾個例子。code

 

建立簡單的動態數據TSQL

  對於如何建立動態TSQL的第一個示例,讓咱們考慮下面的狀況。

  假設你有一個應用程序,用戶界面容許用戶從下拉列表中選擇想要讀取的表。所以,每當有人使用這個接口時,他們就能夠選擇不一樣的表來返回數據。對於本例,咱們假設這個用戶界面顯示來自 AdventureWorks2012 的表格信息:數據庫,用戶選擇表中列出的清單中的代碼使用動態TSQL代碼返回前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);

 

清單1:簡單的動態TSQL示例

 

清單中的代碼首先聲明一個變量名稱@CMD來保存,將要構建的動態選擇語句和@Table變量保存表名。而後將表變量設置爲

讓我來構建個人實際動態

TSQL語句使用SET語句,這個語句設置了變量

對包含選擇的鏈接字符串值的@CMD語句和@Table變量值,而後執行個人動態,使用執行的@CMD變量中包含的TSQL語句聲明。

  爲了進一步測試清單一中的動態TSQL,能夠嘗試一下方法,利用AdventureWork2012 的不一樣,經過更改「集合」中的代碼表@Table,使用語句的語句。

處理更復雜的動態SQL服務器需求

有時你須要編寫一些更復雜的動態TSQL。

在這種狀況下。可能須要作的是何時想要生成代碼來執行某種數據庫維護。當須要爲數據庫維護構建動態的TSQL時,一般會讀取system視圖,而後生成顯示或執行的腳本。假設你是一個數據庫管理員,它接管了一個數據庫,你想要刪除幾個測試表在數據庫中建立的表中都有之前綴「Test」開頭的名稱,以演示如何讀取系統。表視圖和生成適當的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代碼,代碼的最後一部分經過刪除測試數據庫來清理。

  若是你查看第二節中的代碼,你會發現動態TSQL代碼首先打印出它運行的delete語句,而後刪除我在第一部分中建立的測試表。我經過處理一個WHILE循環,在尋找不一樣的表時,從字符串「Test」開始,我構造了一個儲存在變量@CMD中的DELETE命令。而後,我使用PRINT語句來顯示DELETE語句,而後使用EXECUTE語句執行語句。最後一部分,經過刪除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 

報告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應用程序代碼時考慮如下幾點:

•避免SQL Injection攻擊的最佳方法是不使用動態SQL

•編輯用戶輸入的特殊字符參數,如分號和註釋

•只有在須要支持用戶輸入的數據時,才能使參數發生

•若是必須使用動態SQL,則使用使用sp_execute sql的參數化TSQL來執行動態TSQL而不是EXEC。

•增強安全性,只容許執行動態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)',@EnteredParm=@WildCardParm;

 

清單9:使用參數化的TSQL

 

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

•將字符串@CMD更改成再也不包含命令字符串中的@EnteredText變量的值。而是將用戶輸入的文本引入名爲@EnteredParm的變量中。

•添加了一個SET語句來設置變量@WildCardParm將通配符(%)放在@EnteredText參數的開頭和結尾。

•更改了如何執行字符串@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注入攻擊的最佳方法是什麼(最好的方法)?

•不要部署使用動態TSQL的TSQL代碼

•編輯用戶輸入的動態TSQL中用於容許SQL注入攻擊的特殊字符的數據

•使用戶輸入動態TSQL的參數儘量短

•使用參數化的TSQL代碼

問題2

 

用戶可使用SQL注入附件來完成哪些事情(選擇全部適用的內容)?

•返回應用程序無心爲用戶選擇的數據

•將數據插入到應用程序不打算使用的表中

•放一張桌子

•爲新賬戶提供系統管理員權限

• 以上全部

 

問題3

 

若是要部署變量中包含的動態TSQL代碼,最好使用這兩種執行方法中的哪種來最大程度下降SQL注入攻擊的風險?

•執行

•sp_executesql

 

回答:

 

問題1

 

正確的答案是避免SQL注入的最佳方法是不容許您的應用程序中的動態TSQL代碼。 。

 

問題2

 

正確的答案是e,以上全部。使用SQL Injection,惡意用戶能夠執行許多不一樣的SQL操做。它們能夠執行的命令類型取決於用於運行動態TSQL命令的賬戶的權限。若是應用程序賬戶具備sysadmin權限,則SQL Injection攻擊能夠執行用戶想要的任何操做。

 

問題3

 

正確的答案是b。經過使用sp_executesql,您能夠傳遞用戶使用參數輸入數據到參數化的TSQL代碼。

本文是T-SQL樓梯的一部分:超越基礎樓梯

相關文章
相關標籤/搜索