动态Pivot(2)
原文 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; IF OBJECT_ID('dbo.usp_pivot') IS NOT NULL CREATE PROC dbo.usp_pivot DECLARE SET @newline = NCHAR(13) + NCHAR(10); -- 检查是否缺少输入 -- 检查 @on_rows, @on_cols, @agg_col 中的列名称是否存在 IF COLUMNPROPERTY(OBJECT_ID(@object), @on_rows, 'ColumnId') IS NULL -- 检查@agg_func是否是已知的函数 -- 构造列列表 EXEC sp_executesql -- 检查 @cols 是否存在SQL 注入尝试 -- 创建PIVOT查询 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; CREATE VIEW dbo.ViewForPivot SELECT |
然后调用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)的更多相关文章
- 动态Pivot(1)
原文 http://book.51cto.com/art/200710/58874.htm 7.7 动态Pivot 作为另外一个练习,假设你要编写一个存储过程,它生成动态Pivot查询.这个存储过程 ...
- 动态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 ...
- SQL 动态PIVOT查询
DECLARE @sql_str VARCHAR(8000)DECLARE @sql_col VARCHAR(8000) SELECT @sql_col = ISNULL(@sql_col + ',' ...
- Pivot 和 Unpivot
在TSQL中,使用Pivot和Unpivot运算符将一个关系表转换成另外一个关系表,两个命令实现的操作是“相反”的,但是,pivot之后,不能通过unpivot将数据还原.这两个运算符的操作数比较复杂 ...
- SQL Server 动态行转列(参数化表名、分组列、行转列字段、字段值)
一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 实现代码(SQL Codes) 方法一:使用拼接SQL,静态列字段: 方法二:使用拼接SQL, ...
- SQL Server 动态行转列(轉載)
一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 实现代码(SQL Codes) 方法一:使用拼接SQL,静态列字段; 方法二:使用拼接SQL, ...
- SQL server 2005 PIVOT运算符的使用
原文:SQL server 2005 PIVOT运算符的使用 PIVOT,UNPIVOT运算符是SQL server 2005支持的新功能之一,主要用来实现行到列的转换.本文主要介绍PIVOT运算符的 ...
- Sqlserver中PIVOT行转列透视操作
创建表: IF OBJECT_ID('T040_PRODUCT_SALES') IS NOT NULL DROP TABLE T040_PRODUCT_SALES create table T040_ ...
- SQL Server中动态列转行
http://www.cnblogs.com/gaizai/p/3753296.html 一.本文所涉及的内容(Contents) 本文所涉及的内容(Contents) 背景(Contexts) 实现 ...
随机推荐
- U-Boot在FL2440上移植(四)----支持网卡DM9000和烧写yaffs文件系统
<一>支持网卡芯片DM9000 在driver下,有网卡驱动DM9000x.c 和 DM9000x.h DM9000接在BANK4,位宽16 在include/configs/TX2440 ...
- 无法更新 EntitySet“GuigeInfo”,因为它有一个 DefiningQuery,而 <ModificationFunctionMapping> 元素中没有支持当前操作的 <InsertFunction> 元素。
1:实体中必须有主键 2:删除创建的模型重新创建
- BZOJ 1619: [Usaco2008 Nov]Guarding the Farm 保卫牧场
题目 1619: [Usaco2008 Nov]Guarding the Farm 保卫牧场 Time Limit: 5 Sec Memory Limit: 64 MB Submit: 491 S ...
- Binary Tree(二叉树+思维)
Binary Tree Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) Tota ...
- JavaScript螺纹的问题和答案
要求: JavaScript是单线程的,有任务队列.比方使用setTimeou(func,secs)来在secs毫秒后向任务队列加入func.可是,setTimeout后面跟一个死循环,那么死循环导致 ...
- Parrot源代码分析之海贼王
我们的目的是找到speedup-example在使用Parrot加速的原因,假设仅仅说它源于Context Switch的降低,有点简单了,它究竟为什么降低了?除了Context Switch外是否还 ...
- Android,机器狗应用
源码如下: package com.wyl.jqr; import java.io.BufferedReader; import java.io.IOException; import java.io ...
- 0x3f3f3f3f...编程中无穷大常量的设置技巧
转自 http://aikilis.tk/ 如果问题中各数据的范围明确,那么无穷大的设定不是问题,在不明确的情况下,很多程序员都取0x7fffffff作为无穷大,因为这是32-bit int的最大值. ...
- ios中的任务分段
工作比较忙,蛮久没有写东西了,今天我要写的是ios中的任务分段.大多数的情况下,我们用不到任务分段,但是如果我们是在执行比较频繁的函数或者这个函数是比较耗时, 某一条件下,我要执行新的任务,并且取消上 ...
- linux c 得到时间
ctime: 将时间和日期以字符串格式表示头文件: time.h函数定义: char *ctime(const time_t *timep); 应用举例:#include <stdio.h> ...