动态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) 实现 ...
随机推荐
- 解决phpmyadmin配置文件的权限问题
如果部署的phpmyadmin权限为所有人可写,即权限为777,就会报”Wrong permissions on configuration file, should not be world wri ...
- GCC -Wall
官网:http://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Warning-Options.html#Warning-Options3.8 Options to Re ...
- 微软http api说明书地址
https://msdn.microsoft.com/en-us/library/windows/desktop/aa364622(v=vs.85).aspx https://msdn.microso ...
- hibernate 数据关联一对多 3.1
一对多,多对一 (在多的一端存放一的外键) 但是在实体类中不需要创建这个外键 // 在一的一方创建Set集合 public class User { private Integer id; priva ...
- 利用Mono.Cecil动态修改程序集来破解商业组件(仅用于研究学习)
原文 利用Mono.Cecil动态修改程序集来破解商业组件(仅用于研究学习) Mono.Cecil是一个强大的MSIL的注入工具,利用它可以实现动态创建程序集,也可以实现拦截器横向切入动态方法,甚至还 ...
- Python基础 3----文件和网络
1 Python文件IO 1 标转的输入和输出 print 方法:输出数据到标准的输出 raw_input 方法:标准的输入 2 file文件类 读文件 f = file(文件名, 模式) 有5种模式 ...
- 循环次数( M - 暴力求解、打表)
循环次数 Description 我们知道,在编程中,我们时常需要考虑到时间复杂度,特别是对于循环的部分.例如, 如果代码中出现 for(i=1;i ...
- 断开/删除 SVN 链接(.svn)的几种方法
上传到正式的服务器时需要去掉这些不必要的文件,找到了几种方法: 1.windows下: xcopy project_dir project_dir_1 /s /i (从project_dir 复制文件 ...
- C++编译时函数名修饰约定规则(很具体),MFC提供的宏,extern "C"的作用
调用约定: __cdecl __fastcall与 __stdcall,三者都是调用约定(Calling convention),它决定以下内容:1)函数参数的压栈顺序,2)由调用者还是被调用者把参数 ...
- ASP.NET MVC 5 学习教程:Details 和 Delete 方法详解
原文 ASP.NET MVC 5 学习教程:Details 和 Delete 方法详解 在教程的这一部分,我们将研究一下自动生成的 Details 和Delete 方法. Details 方法 打开M ...