[翻译]通往T-SQL的楼梯
Posted 我是墩
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[翻译]通往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时如何动态生成它。我还将探讨SQL注入和如何避免当你的动态TSQL代码遭到注入式攻击。
什么是动态的TSQL并且你为什么想要使用它?
动态TSQL究竟是什么?每次你运行动态TSQL时都可能是不同的代码。基于批处理中的某些条件或参数创建了即时生成的代码。基于批处理中的某些条件或参数创建了即时生成的代码。当“条件或参数不同的TSQL代码产生不同的TSQL来执行。
通常当你使用动态TSQL时,你要以编程的方式确定你需要基于数据库中的表的参数或数据。动态TSQL的用途是无止境的。当你可能想要使用动态TSQL时,这里有两个例子:
1、您希望用户从下拉列表中选择一些可能导致查询运行不同的标准,如排序顺序。
2、应用程序不知道要运行的表的名称,直到运行时为止。
动态TSQL可以用来代替由于TSQL语言不允许你使用变量或参数的特定表或列的名称。
为了更好地了解动态TSQL让我们看几个例子。
创建简单动态T SQL
对于第一个例子,如何创建动态TSQL让我们考虑下面的情况。假设您有一个应用程序,其中用户界面允许用户从下拉列表中选择要读取的表。因此,每次有人使用接口时,他们都可以选择一个不同的表,从中返回数据。在这个例子中我们假设从adventureworks2012数据库显示的这个用户界面表的信息数据库和用户选择的adventureworks2012.sales.salesorderdetail表。清单1中的代码显示了一个使用动态TSQL代码返回的前10 adventureworks.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:简单的动态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的楼梯的主要内容,如果未能解决你的问题,请参考以下文章
#翻译#通往t - sql的阶梯:超越基本级别3:构建相关子查询,原文链接:Stairway to T-SQL: Beyond The Basics Level 3: Building a Corre