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代码中。

相关文章
相关标签/搜索