原文  http://book.51cto.com/art/200710/58875.htm

存储过程sp_pivot的实现包含糟糕的编程习惯和安全隐患。就像我在本章的前面提到的,微软强烈建议不要在用户定义存储过程的名称中使用sp_前缀。一方面,把存储过程创建为特殊存储过程的会带来灵活性;但另一方面,你所依赖的行为得不到任何支持。所以最好放弃这种通过创建以sp_为前缀的存储过程获取的灵活性,在用户数据库中使用其他前缀创建用户定义存储过程。
代码定义的所有输入参数都未限制大小(使用MAX说明符),而且未作任何输入验证。因为存储过程调用的动态执行基于用户输入的字符串,限制输入的大小并检查潜在的SQL注入危险是非常重要的。对于现有的实现,黑客可以很容易地注入代码并破坏你的系统。你可以在第4章和联机丛书(http://msdn2.microsoft.com/en-us/library/ms161953 (SQL.90).aspx)中找到关于SQL注入的讨论。作为一个利用用户输入注入恶意代码的示例,观察下面这个对存储过程的调用。

EXEC Northwind.dbo.sp_pivot
@query    = N'dbo.Orders',
@on_rows  = N'1 AS dummy_col) DummyTable;
PRINT ''So easy to inject code here!
This could have been a DROP TABLE or xp_cmdshell command!'';
SELECT * FROM (select EmployeeID AS empid',
@on_cols  = N'MONTH(OrderDate)',
@agg_func = N'COUNT',
@agg_col  = N'*';

存储过程生成的查询字符串应该是这样的:

SELECT *
FROM
( SELECT
1 AS dummy_col) DummyTable;
PRINT 'So easy to inject code here!
This could have been a DROP TABLE or xp_cmdshell command!';
SELECT * FROM (select EmployeeID AS empid,
MONTH(OrderDate) AS pivot_col,
1 AS agg_col
FROM
( SELECT * FROM dbo.Orders
) AS Query
) AS PivotInput
PIVOT
( COUNT(agg_col)
FOR pivot_col
IN([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12])
) AS PivotOutput;

当执行这些代码时,注入的PRINT语句可以顺利执行。为了证明可以轻易地注入代码,我使用了一个无害的PRINT语句,但是很明显,这些恶意代码可以是任何有效的T-SQL代码。例如DROP TABLE语句、调用xp_cmdshell等。总之,在这些地方采取措施防范SQL注入是极其重要的。
该存储过程不仅未防范SQL注入,而且根本就没有执行任何输入验证。例如,应该验证输入的对象和列名称的有效性。该存储过程也没有包含错误处理。我会在第10章讨论错误处理,所以没有在修改后的解决方案中演示这一点。下面我将演示输入验证。

在呈现修改后的解决方案之前,先删除已经存在的sp_pivot:

USE master;
GO
IF OBJECT_ID('dbo.sp_pivot') IS NOT NULL
DROP PROC dbo.sp_pivot;

代码清单7-9是该任务的修改后的解决方案
代码清单7-9  创建usp_pivot存储过程的脚本

USE Northwind;
GO

IF OBJECT_ID('dbo.usp_pivot') IS NOT NULL
DROP PROC dbo.usp_pivot;
GO

CREATE PROC dbo.usp_pivot
@schema_name AS sysname      = N'dbo',
-- 表/视图的架构
@object_name AS sysname      = NULL, 
-- 表/视图的名称
@on_rows     AS sysname      = NULL, -- 分组列
@on_cols     AS sysname      = NULL, -- 旋转列
@agg_func    AS NVARCHAR(12) = N'MAX',
-- 聚集函数
@agg_col     AS sysname      = NULL -- 统计列
AS

DECLARE
@object  AS NVARCHAR(600),
@sql     AS NVARCHAR(MAX),
@cols    AS NVARCHAR(MAX),
@newline AS NVARCHAR(2),
@msg     AS NVARCHAR(500);

SET @newline = NCHAR(13) + NCHAR(10);
SET @object  = QUOTENAME(@schema_name) + N'.' + QUOTENAME(@object_name);

-- 检查是否缺少输入
IF   @schema_name IS NULL
OR @object_name IS NULL
OR @on_rows     IS NULL
OR @on_cols     IS NULL
OR @agg_func    IS NULL
OR @agg_col     IS NULL
BEGIN
SET @msg = N'Missing input parameters: '
+ CASE WHEN @schema_name IS NULL 
THEN N'@schema_name;' ELSE N'' END
+ CASE WHEN @object_name IS NULL 
THEN N'@object_name;' ELSE N'' END
+ CASE WHEN @on_rows     IS NULL 
THEN N'@on_rows;'     ELSE N'' END
+ CASE WHEN @on_cols     IS NULL 
THEN N'@on_cols;'     ELSE N'' END
+ CASE WHEN @agg_func    IS NULL 
THEN N'@agg_func;'    ELSE N'' END
+ CASE WHEN @agg_col     IS NULL 
THEN N'@agg_col;'     ELSE N'' END
RAISERROR(@msg, 16, 1);
RETURN;
END 
-- 只允许已存在的表或视图作为输入对象
IF COALESCE(OBJECT_ID(@object, N'U'),
OBJECT_ID(@object, N'V')) IS NULL
BEGIN
SET @msg = N'%s is not an existing table or view in the database.';
RAISERROR(@msg, 16, 1, @object);
RETURN;
END

-- 检查 @on_rows, @on_cols, @agg_col 中的列名称是否存在

IF   COLUMNPROPERTY(OBJECT_ID(@object), @on_rows, 'ColumnId') IS NULL
OR COLUMNPROPERTY(OBJECT_ID(@object), @on_cols, 'ColumnId') IS NULL
OR COLUMNPROPERTY(OBJECT_ID(@object), @agg_col, 'ColumnId') IS NULL
BEGIN
SET @msg = N'%s, %s and %s must'
+ N' be existing column names in %s.';
RAISERROR(@msg, 16, 1, @on_rows, @on_cols, @agg_col, @object);
RETURN;
END

-- 检查@agg_func是否是已知的函数
-- 根据需要增加该清单并相应调整@agg_func的大小 
IF @agg_func NOT IN
(N'AVG', N'COUNT', N'COUNT_BIG', N'SUM', N'MIN', N'MAX',
N'STDEV', N'STDEVP', N'VAR', N'VARP')
BEGIN
SET @msg = N'%s is an unsupported aggregate function.';
RAISERROR(@msg, 16, 1, @agg_func);
RETURN;
END

-- 构造列列表
SET @sql =
N'SET @result = '                                    + @newline +
N'  STUFF('                                          + @newline +
N'    (SELECT N'','' + '
+ N'QUOTENAME(pivot_col) AS [text()]'       + @newline +
N'     FROM (SELECT DISTINCT('
+ QUOTENAME(@on_cols) + N') AS pivot_col'   + @newline +
N'           FROM ' + @object + N') AS DistinctCols' + @newline +
N'     ORDER BY pivot_col'                           + @newline +
N'     FOR XML PATH('''')),'                         + @newline +
N'    1, 1, N'''');'

EXEC sp_executesql
@stmt   = @sql,
@params = N'@result AS NVARCHAR(MAX) OUTPUT',
@result = @cols OUTPUT;

-- 检查 @cols 是否存在SQL 注入尝试
IF   UPPER(@cols) LIKE UPPER(N'%0x%')
OR UPPER(@cols) LIKE UPPER(N'%;%')
OR UPPER(@cols) LIKE UPPER(N'%''%')
OR UPPER(@cols) LIKE UPPER(N'%--%')
OR UPPER(@cols) LIKE UPPER(N'%/*%*/%') 
OR UPPER(@cols) LIKE UPPER(N'%EXEC%')
OR UPPER(@cols) LIKE UPPER(N'%xp[_]%')
OR UPPER(@cols) LIKE UPPER(N'%sp[_]%')
OR UPPER(@cols) LIKE UPPER(N'%SELECT%')
OR UPPER(@cols) LIKE UPPER(N'%INSERT%')
OR UPPER(@cols) LIKE UPPER(N'%UPDATE%')
OR UPPER(@cols) LIKE UPPER(N'%DELETE%')
OR UPPER(@cols) LIKE UPPER(N'%TRUNCATE%')
OR UPPER(@cols) LIKE UPPER(N'%CREATE%')
OR UPPER(@cols) LIKE UPPER(N'%ALTER%')
OR UPPER(@cols) LIKE UPPER(N'%DROP%')
-- 其他一些可能用于SQL注入的字符串
BEGIN
SET @msg = N'Possible SQL injection attempt.';
RAISERROR(@msg, 16, 1);
RETURN;
END

-- 创建PIVOT查询
SET @sql =
N'SELECT *'                                          + @newline +
N'FROM'                                              + @newline +
N'  ( SELECT '                                       + @newline +
N'      ' + QUOTENAME(@on_rows) + N','               + @newline +
N'      ' + QUOTENAME(@on_cols) + N' AS pivot_col,'  + @newline +
N'      ' + QUOTENAME(@agg_col) + N' AS agg_col'     + @newline +
N'    FROM ' + @object                               + @newline +
N'  ) AS PivotInput'                                 + @newline +
N'  PIVOT'                                           + @newline +
N'    ( ' + @agg_func + N'(agg_col)'                 + @newline +
N'      FOR pivot_col'                               + @newline +
N'        IN(' + @cols + N')'                        + @newline +
N'    ) AS PivotOutput;';

EXEC sp_executesql @sql;

该存储过程的实现遵循了良好的编程习惯并解决了前面提到的安全缺陷。但是要记住,当根据用户输入和存储数据/元数据构造代码时,要完全地防范SQL注入是非常困难的。

存储过程usp_pivot是在Northwind数据库中以usp_前缀创建的用户定义存储过程。这意味着它只能与Northwind中的表和视图进行交互,从这个意义来讲,它不如前面的实现那样灵活。但是你可以在Northwind中创建用于查询其他数据库对象的视图,并把该视图作为输入提供给这个存储过程。

usp_pivot存储过程的代码提供了几种防范SQL注入尝试的措施:

 限制输入参数的大小。
 存储过程只接收在数据库中存在的有效的表或视图名称,不接收其他形式的查询。同样,存储过程中的输入参数@on_rows、@on_cols和@agg_co只接收在输入表/视图中存在的有效的列名称,不能是任意的T-SQL表达式。你可以使用任意的查询创建视图,然后把它作为该存储过程的输入。
 代码在引用对象和列名称的地方使用了QUOTENAME,并用方括号作为分隔标识符。
 存储过程的代码检查@cols变量是否存在注入的代码字符串,它们可能通过存储被串联起来的旋转列值注入。
代码还对输入执行检查以确保提供了所有参数,这些表/视图和列名称存在,输入的聚集函数包含在支持的函数列表中。关于错误处理,我将在第10章再讨论。

usp_pivot存储过程看起来没有sp_pivot灵活,但你可以创建视图为usp_pivot提供数据。例如,考虑下面的代码,在前面曾用它返回按订单年份旋转的每个员工的订单金额合计(数量*单价):

EXEC Northwind.dbo.sp_pivot
@query    = N'
SELECT O.OrderID, EmployeeID, OrderDate, Quantity, UnitPrice
FROM dbo.Orders AS O
JOIN dbo.[Order Details] AS OD
ON OD.OrderID = O.OrderID',
@on_rows  = N'EmployeeID AS empid',
@on_cols  = N'YEAR(OrderDate)',
@agg_func = N'SUM',
@agg_col  = N'Quantity*UnitPrice';

通过创建一个包含所需数据的视图,你就可以利用usp_pivot实现同样的功能。

USE Northwind;
GO
IF OBJECT_ID('dbo.ViewForPivot') IS NOT NULL
DROP VIEW dbo.ViewForPivot;
GO

CREATE VIEW dbo.ViewForPivot
AS

SELECT
O.OrderID       AS orderid,
EmployeeID      AS empid,
YEAR(OrderDate) AS order_year,
Quantity * UnitPrice AS val
FROM dbo.Orders AS O
JOIN dbo.[Order Details] AS OD
ON OD.OrderID = O.OrderID;
GO

然后调用usp_pivot,就像这样:

EXEC dbo.usp_pivot
@object_name = N'ViewForPivot',
@on_rows  = N'empid',
@on_cols  = N'order_year',
@agg_func = N'SUM',
@agg_col  = N'val';

你将得到前面表7-13所示的输出。
相对于你的系统安全而言,这只是很小的代价。
完成后,运行下面的代码进行清理。

USE Northwind;
GO
IF OBJECT_ID('dbo.ViewForPivot') IS NOT NULL
DROP VIEW dbo.ViewForPivot;
GO
IF OBJECT_ID('dbo.usp_pivot') IS NOT NULL
DROP PROC dbo.usp_pivot;

动态Pivot(2)的更多相关文章

  1. 动态Pivot(1)

    原文 http://book.51cto.com/art/200710/58874.htm 7.7  动态Pivot 作为另外一个练习,假设你要编写一个存储过程,它生成动态Pivot查询.这个存储过程 ...

  2. 动态PIVOT行转列

    id name subject score remark1 l math 86 2 l eng 68 3 l phy 88 4 z chn 99 5 z math 92 6 z com 98 7 z ...

  3. SQL 动态PIVOT查询

    DECLARE @sql_str VARCHAR(8000)DECLARE @sql_col VARCHAR(8000) SELECT @sql_col = ISNULL(@sql_col + ',' ...

  4. Pivot 和 Unpivot

    在TSQL中,使用Pivot和Unpivot运算符将一个关系表转换成另外一个关系表,两个命令实现的操作是“相反”的,但是,pivot之后,不能通过unpivot将数据还原.这两个运算符的操作数比较复杂 ...

  5. SQL Server 动态行转列(参数化表名、分组列、行转列字段、字段值)

    一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 实现代码(SQL Codes) 方法一:使用拼接SQL,静态列字段: 方法二:使用拼接SQL, ...

  6. SQL Server 动态行转列(轉載)

    一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 实现代码(SQL Codes) 方法一:使用拼接SQL,静态列字段; 方法二:使用拼接SQL, ...

  7. SQL server 2005 PIVOT运算符的使用

    原文:SQL server 2005 PIVOT运算符的使用 PIVOT,UNPIVOT运算符是SQL server 2005支持的新功能之一,主要用来实现行到列的转换.本文主要介绍PIVOT运算符的 ...

  8. Sqlserver中PIVOT行转列透视操作

    创建表: IF OBJECT_ID('T040_PRODUCT_SALES') IS NOT NULL DROP TABLE T040_PRODUCT_SALES create table T040_ ...

  9. SQL Server中动态列转行

    http://www.cnblogs.com/gaizai/p/3753296.html 一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 实现 ...

随机推荐

  1. SSL证书与Https应用部署小结

    为了提高网站的安全性,一般会在比较敏感的部分页面采用https传输,比如注册.登录.控制台等.像Gmail.网银等全部采用https传输. https/ssl 主要起到两个作用:网站认证.内容加密传输 ...

  2. CloudStack修复bug

    CloudStack应用越来越广,但是随着测试也遇到了越来越多的bug. 不想等待新版本发布而且又急于修复某些bug的童鞋,可以参考下本文内容. CloudStack是java语言写成,发布时会发布为 ...

  3. 基于visual Studio2013解决C语言竞赛题之0203格式化输出

     题目 解决代码及点评 #include <stdio.h> #include <stdlib.h> void main() { // print是输出函数,参数%s表示输 ...

  4. IT第十一天、第十二天、第十三天 - 数组的应用、飞行棋游戏的编写和总结

    NIIT第十一天 上午 多维数组 1.数组是引用数据类型 排序 1.冒泡排序法 2.类冒泡排序法 下午 飞行棋游戏 1.项目策划 2.项目规则确认 3.项目模块确认 晚上 1.飞行棋游戏,项目框架的编 ...

  5. Hdu 3371 Connect the Cities(最小生成树)

    地址:http://acm.hdu.edu.cn/showproblem.php?pid=3371 其实就是最小生成树,但是这其中有值得注意的地方:就是重边.题目没有告诉你两个城市之间只有一条路可走, ...

  6. ACM 中常用的算法有哪些?

    在网上看到别人ACM学习的心得,转载过来,源地址不记得了,当时是百度的.内容如下: 网络上流传的答案有很多,估计提问者也曾经去网上搜过.所以根据自己微薄的经验提点看法. 我ACM初期是训练编码能力,以 ...

  7. How many integers can you find(容斥+dfs容斥)

    How many integers can you find Time Limit: 12000/5000 MS (Java/Others)    Memory Limit: 65536/32768 ...

  8. Asteroids(最小点覆盖)

    Asteroids Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 18183   Accepted: 9905 Descri ...

  9. 写了交互给后台后来不能用?bug多多多又找不到文件效率低?工作流程帮你优化起来~~~~

    前端工作流程(多方交互篇) 新的网页: 1.跟美工沟通,跟产品沟通,跟后台沟通.前两者主要是页面样式.后者主要是表单交互.用哪个框架之类的. 2.实现.(写清楚哪块是用什么验证方式的)→ 给后台. 3 ...

  10. 多线程之Future模式

    详细参见葛一名老师的<Java程序性能优化> Futrue模式:对于多线程,如果线程A要等待线程B的结果,那么线程A没必要等待B,直到B有结果,可以先拿到一个未来的Future,等B有结果 ...