SQL SERVER儲存過程加密和安全上下文
對SQL Server 2008的安全入門略作小結,以作備忘。本文涉及兩個應用:儲存過程加密和安全上下文。
<一>儲存過程加密
SQL server,我已經成了儲存過程的忠實擁躉。在直接使用SQL語句還是儲存過程來處理業務邏輯時,我基本會毫不猶豫地選擇後者。
理由如下:
1、使用儲存過程,至少在防非法注入(inject)方面提供更好的保護。至少,儲存過程在執行前,首先會執行預編譯,(如果由於非法引數的原因)編譯出錯則不會執行,這在某種程度上提供一層天然的屏障。
我採用的一個許可權控制系統就是通過拼湊一個SQL語句,最終得到了一個形如“ where 1=1 and dataID in (1,2) and ModelID in (2,455) And ShopID in (111) and departID in ( 1,3) and ([Name] like %myword%) ”的where條件子句來獲取符合條件的結果集。
注意:這個引數是通過位址列web應用的位址列或Winform的UI介面來輸入的,所以對惡意注入需要花費一定的成本來維護。因為一些常用的關鍵字(或敏感詞)很難區分是惡意或非惡意。
2、使用儲存過程而不是直接訪問基表,可以提供更好的安全性。你可以在行級或列級控制資料如何被修改。相對於表的訪問,你可以確認有執行許可權許可的使用者執行相應的儲存過程。這也是訪問資料伺服器的惟一呼叫途徑。因此,任何偷窺者將無法看到你的SELECT語句。換句話說,每個應用只能擁有相應的儲存過程來訪問基表,而不是“SLEECT *”。
3、儲存過程可以加密。(這點非常實用,設想一下,您的資料庫伺服器是託管的或租用的,你是否能心安理得的每天睡個安穩覺。如果競爭對手“一不小心”登上你的SQL Server,或通過注入得到了你的儲存過程,然後相應的注入惡意的SQL,將您的業務邏輯亂改一通,而恰巧您五分鐘前又沒做備份,那會怎麼樣?)
(注意:加密儲存過程前應該備份原始儲存過程,且加密應該在部署到生產環境前完成。)
儲存過程的加密非常簡單,我們看一個例子:
插入測試表
[ruby] view plaincopyprint?
- use testDb2
- go
- /**********測試表*****************/
- SET ANSI_PADDING ON
- GO
- CREATE TABLE [dbo].[tb_demo](
- [id] [int] NOT NULL,
- [submitdate] [datetime] NULL,
- [commment] [nvarchar](200) NULL,
- )
- GO
- SET ANSI_PADDING OFF
- GO
- Insert into [tb_demo]
- select 1024, getdate(),REPLICATE('A',100);
- WAITFOR DELAY '00:00:04';
- Insert into [tb_demo]
- select 1024, getdate(),REPLICATE('B',50);
- go
- use testDb2
- go
- /**********測試表*****************/
- SET ANSI_PADDING ON
- GO
- CREATE TABLE [dbo].[tb_demo](
- [id] [int] NOT NULL,
- [submitdate] [datetime] NULL,
- [commment] [nvarchar](200) NULL,
- )
- GO
- SET ANSI_PADDING OFF
- GO
- Insert into [tb_demo]
- select 1024, getdate(),REPLICATE('A',100);
- WAITFOR DELAY '00:00:04';
- Insert into [tb_demo]
- select 1024, getdate(),REPLICATE('B',50);
- go
插入儲存過程:
[ruby] view plaincopyprint?
- /***************建立未加密的儲存過程*******************/
- Create Procedure CPP_test_Original
- AS
- select * from [tb_demo]
- go
- /***************建立加密的儲存過程*******************/
- Create Procedure CPP_test_Encryption
- with encryption
- AS
- ----可以換成任意的邏輯
- execute CPP_test_Original
- go
- /***************建立未加密的儲存過程*******************/
- Create Procedure CPP_test_Original
- AS
- select * from [tb_demo]
- go
- /***************建立加密的儲存過程*******************/
- Create Procedure CPP_test_Encryption
- with encryption
- AS
- ----可以換成任意的邏輯
- execute CPP_test_Original
- go
未加密的儲存過程:
加密的儲存過程:
此時,至少,儲存過程的內容不會被輕易看到(雖然解密也是有可能的)。應用這個,我們可以對某些關鍵的儲存過程進行加密。但此時,儲存過程仍然能被execute、alter和drop。
<二>安全上下文
除了加密sql文字的內容,我們還可以使用EXECUTE AS 子句設定儲存過程的安全上下文,以滿足不同的安全級別需求。
如果你對這些不感興趣,請直接路過帶下劃線的段落。
(關於EXECUTE AS 子句的詳細用法,請參看MSDN:http://msdn.microsoft.com/zh-cn/library/ms188354.aspx)
此處,我們需要了解的是:
1、在 SQL Server 中,可以定義以下使用者定義模組的執行上下文:函式(內聯表值函式除外)、過程、佇列和觸發器。
通過指定執行模組的上下文,可以控制資料庫引擎使用哪一個使用者帳戶來驗證對模組引用的物件的許可權。這有助於人們更靈活、有力地管理使用者定義的模組及其所引用物件所形成的物件鏈中的許可權。必須而且只需授予使用者對模組自身的許可權,而無需授予使用者對被引用物件的顯式許可權。只有執行模組的使用者必須對模組訪問的物件擁有許可權。
針對函式、過程、佇列和觸發器,對應的引數也不同。儲存過程對應的引數包括(CALLER | SELF | OWNER | 'user_name')。
■CALLER 指定模組內的語句在模組呼叫方的上下文中執行。執行模組的使用者不僅必須對模組本身擁有適當的許可權,還要對模組引用的任何資料庫物件擁有適當許可權。 CALLER 是除佇列外的所有模組的預設值,與 SQL Server 2005 行為相同。 CALLER 不能在 CREATE QUEUE 或 ALTER QUEUE 語句中指定。■SELF EXECUTE AS SELF 與 EXECUTE AS user_name 等價,其中指定使用者是建立或更改模組的使用者。建立或更改模組的使用者的實際使用者 ID 儲存在 sys.sql_modules 或 sys.service_queues 目錄檢視的 execute_as_principal_id列中。SELF 是佇列的預設值。■OWNER 指定模組內的語句在模組的當前所有者上下文中執行。如果模組沒有指定的所有者,則使用模組架構的所有者。不能為 DDL 或登入觸發器指定 OWNER。注意:OWNER 必須對映到單獨帳戶,不能是角色或組。■'user_name' 指定模組內的語句在 user_name 指定的使用者的上下文中執行。將根據 user_name 來驗證對模組內任意物件的許可權。不能為具有伺服器作用域的 DDL 觸發器或登入觸發器指定 user_name。請改用 login_name。user_name 必須存在於當前資料庫中,並且必須是單獨帳戶。user_name 不能是組、角色、證書、金鑰或內建帳戶,如 NT AUTHORITY/LocalService、NT AUTHORITY/NetworkService 或 NT AUTHORITY/LocalSystem。執行上下文的使用者 ID 儲存在元資料中,可以在 sys.sql_modules 或 sys.assembly_modules 目錄檢視的 execute_as_principal_id 列檢視。2、所有權鏈具有以下限制: 僅適用於 DML 語句:SELECT、INSERT、UPDATE 和 DELETE。 呼叫和被呼叫物件的所有者必須相同。 不適用於模組內的動態查詢。我們看一個示例:第一步、建立一個測試儲存過程,用來delete表tb_Demo的所有資料
[ruby] view plaincopyprint?
- USE testDb2
- GO
- CREATE PROCEDURE dbo.[CPP_DEL_ALL_Tb_Demo]
- AS
- -- Deletes all rows prior to the data feed
- DELETE dbo.[tb_Demo]
- GO
- USE testDb2
- GO
- CREATE PROCEDURE dbo.[CPP_DEL_ALL_Tb_Demo]
- AS
- -- Deletes all rows prior to the data feed
- DELETE dbo.[tb_Demo]
- GO
第二步:建立一個賬號TonyZhang,並賦於該賬號對該儲存過程的exec許可權
[ruby] view plaincopyprint?
- USE master
- GO
- CREATE LOGIN TonyZhang WITH PASSWORD = '123b3b4'
- USE testDb2
- GO
- CREATE USER TonyZhang
- GO
- GRANT EXEC ON dbo.[CPP_DEL_ALL_Tb_Demo] to TonyZhang
- USE master
- GO
- CREATE LOGIN TonyZhang WITH PASSWORD = '123b3b4'
- USE testDb2
- GO
- CREATE USER TonyZhang
- GO
- GRANT EXEC ON dbo.[CPP_DEL_ALL_Tb_Demo] to TonyZhang
以該賬號登入SQL Server,並執行:
[ruby] view plaincopyprint?
- EXECUTE dbo.CPP_DEL_ALL_Tb_Demo
- /**
- (4 row(s) affected)
- **/
- EXECUTE dbo.CPP_DEL_ALL_Tb_Demo
- /**
- (4 row(s) affected)
- **/
注意:此時,雖然TonyZhang除了執行儲存過程[CPP_DEL_ALL_Tb_Demo]之外沒有任何其他許可權,但仍然執行了儲存過程,並刪除了表記錄。如果我們修改儲存過程為:
[ruby] view plaincopyprint?
- Alter PROCEDURE dbo.[CPP_DEL_ALL_Tb_Demo]
- AS
- -- Deletes all rows prior to the data feed
- truncate table dbo.[tb_Demo]
- GO
- Alter PROCEDURE dbo.[CPP_DEL_ALL_Tb_Demo]
- AS
- -- Deletes all rows prior to the data feed
- truncate table dbo.[tb_Demo]
- GO
此時,再以TonyZhang登入,並執行儲存過程,會提示:這是因為所有者權鏈只限定在SELECT、INSERT、UPDATE 和 DELETE。而不包括Truncate,換句話說,系統授於的Exec只既定於SELECT、INSERT、UPDATE 和 DELETE有人可能會問:如果在儲存過程內部呼叫動態語句,而不是明確的表名,我們如何限定許可權呢?第三步:我們建立一個儲存過程,功能是傳入一個引數表名,查詢該表的記錄數。
[ruby] view plaincopyprint?
- CREATE PROCEDURE dbo.[CPP_SEL_CountRowsFromAnyTable]
- @SchemaAndTable nvarchar(255)
- AS
- EXEC ('SELECT COUNT(1) FROM ' + @SchemaAndTable)
- GO
- CREATE PROCEDURE dbo.[CPP_SEL_CountRowsFromAnyTable]
- @SchemaAndTable nvarchar(255)
- AS
- EXEC ('SELECT COUNT(1) FROM ' + @SchemaAndTable)
- GO
授於Tonyzhang 以執行該儲存過程的許可權:
[ruby] view plaincopyprint?
- GRANT EXEC ON dbo.[CPP_SEL_CountRowsFromAnyTable] to TonyZhang
- go
- GRANT EXEC ON dbo.[CPP_SEL_CountRowsFromAnyTable] to TonyZhang
- go
此時,以Tonyzhang登入,執行儲存過程,會提示:注意,此時,tonyzhang雖然有執行儲存過程的許可權,但是沒有引數表的slect許可權,所以執行失敗。第四步:修改儲存過程的上下文建立一個新賬號jackwang,賦於表tb_Demo的select許可權
[ruby] view plaincopyprint?
- USE master
- GO
- CREATE LOGIN JackWang WITH PASSWORD = '123b3b4'
- USE Testdb2
- GO
- CREATE USER JackWang
- GRANT SELECT ON OBJECT::dbo.[tb_Demo] TO JackWang
- GO
- /*******
- 注意:此時,JackWang 可以執行dbo.[tb_Demo的Select
- *******/
- USE master
- GO
- CREATE LOGIN JackWang WITH PASSWORD = '123b3b4'
- USE Testdb2
- GO
- CREATE USER JackWang
- GRANT SELECT ON OBJECT::dbo.[tb_Demo] TO JackWang
- GO
- /*******
- 注意:此時,JackWang 可以執行dbo.[tb_Demo的Select
- *******/
修改儲存的執行者
[ruby] view plaincopyprint?
- USE Testdb2
- GO
- alter PROCEDURE dbo.[CPP_SEL_CountRowsFromAnyTable]
- @SchemaAndTable nvarchar(255)
- WITH EXECUTE AS 'JackWang'
- AS
- EXEC ('SELECT COUNT(1) FROM ' + @SchemaAndTable)
- GO
- USE Testdb2
- GO
- alter PROCEDURE dbo.[CPP_SEL_CountRowsFromAnyTable]
- @SchemaAndTable nvarchar(255)
- WITH EXECUTE AS 'JackWang'
- AS
- EXEC ('SELECT COUNT(1) FROM ' + @SchemaAndTable)
- GO
注意:這樣,我們再呼叫儲存過程[CPP_SEL_CountRowsFromAnyTable]時,會自動以JackWang的身份執行該儲存過程。此時,我們仍以Tonyzhang登入,再執行:小結:本文通過簡單的兩個示例開始SQL server程式碼的安全之旅,1、儲存過程的加密,(注意:加密儲存過程前應該備份原始儲存過程,且加密應該在部署到生產環境前完成。)2、儲存過程的安全上下文。可以通過上下文設定更加嚴格的資料訪問級別。(主要是對SELECT、INSERT、UPDATE 和 DELETE語句的訪問限制)後續部分將會涉及SQL server 2008新增的透明加密(TDE)功能。