动态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) 实现 ...
随机推荐
- python3基本框架
- 转:FileReader详解与实例---读取并显示图像文件
~~~针对需要读取本地图像,并立即显示在浏览器的情况,由于chrome firefox出于安全限制,input file并不返回文件的真实路径,经测试IE6/7/8都会返回真实路径,所以chrome, ...
- GDB调试精粹及使用实例
一:列文件清单 1. List (gdb) list line1,line2 二:执行程序 要想运行准备调试的程序,可使用run命令,在它后面可以跟随发给该程序的任何参数,包括标准输入和标准输出说明符 ...
- 使用FreeType实现矢量字体的粗体、斜体、描边、阴影效果
前言: Freetype是一个跨平台.开源的字体渲染器,网上很多文章介绍,本人就不啰嗦了.本文重点在于实现文章标题所属的各种效果,不是Freetype的基本使用方法介绍文档,所以对于Freetype不 ...
- BZOJ 3240([Noi2013]矩阵游戏-费马小定理【矩阵推论】-%*s-快速读入)
3240: [Noi2013]矩阵游戏 Time Limit: 10 Sec Memory Limit: 256 MB Submit: 123 Solved: 73 [ Submit][ St ...
- android 实现蓝牙自动配对连接
BluetoothConnectActivityReceiver.java:监听蓝牙配对的广播 代码: package com.imte.Broadcast; import com.imte.util ...
- BZOJ 3668: [Noi2014]起床困难综合症( 贪心 )
之前以为xor,or,and满足结合律...然后连样例都过不了 早上上体育课的时候突然想出来了...直接处理每一位是1,0的最后结果, 然后从高位到低位贪心就可以了... 滚去吃饭了.. ------ ...
- iOS 8 强制横屏
最近用到视频播放功能:(Vitamio, 注:在Build Setting 里面的 Other Link Flag 添加-all_load) iOS 8的屏幕旋转比较坑, 使用以下代码可以强制旋转 - ...
- C语言深度剖析---const关键字(转载)
const是constant的缩写,是恒定不变的意思.被const修饰的值,是只读变量. 1.const修饰只读变量,具有不变性 #include <stdio.h> int m ...
- 在SSH整合框架中经常会用到Service层,请问大家这个Service层有什么具体的作用,可不可以不用这个Service层呢?
有效地分离数据访问层(DAO)和业务层(SERVICE),使之各司其职,举例说明:如果DAO层访问数据库,得到的数据根据业务需要要进行加密,那么取数据和把数据加密就是2个步骤,访问数据和业务逻辑加工, ...