在保密你的伺服器和數據,防備當前複雜的攻擊,SQL Server有你需要的一切。但在你能有效使用這些安全功能前,你需要理解你面對的威脅和一些基本的安全概念。這篇文章提供了基礎,因此你可以對SQL Server里的安全功能充分利用,不用在面對特定威脅,不能保護你數據的功能上浪費時間。 SQL Serv ...
在保密你的伺服器和數據,防備當前複雜的攻擊,SQL Server有你需要的一切。但在你能有效使用這些安全功能前,你需要理解你面對的威脅和一些基本的安全概念。這篇文章提供了基礎,因此你可以對SQL Server里的安全功能充分利用,不用在面對特定威脅,不能保護你數據的功能上浪費時間。
SQL Server決定主體是否有需要的許可執行代碼的基本方式是它的執行上下文角色。這都是複雜的可能性,主體有執行代碼的許可,但沒有代碼訪問的潛在對象的許可,例如表裡的數據。這篇文章會探尋SQL Server執行上下文,所有權鏈接接,模擬,還有向你展示下如果通過T-SQL代碼控制數據訪問。
執行上下文
當用戶執行一個存儲過程或其它資料庫代碼時,SQL Server檢查確保,不僅用戶有運行存儲過程的許可,而且有代碼訪問使用資料庫對象的許可。沒有這類許可檢查,有些人會很容易創建可以讀取表,使用執行代碼訪問不需要其它的對象代碼。這會是重大的安全漏洞。
這個許可檢查過程不發生的唯一例外是代碼的所有者也是代碼訪問的所有潛在對象的所有者。在這個共同所有權下,SQL Server驗證調用者在代碼上有EXECUTE許可,不會繼續檢查許可。
例如,如果在存儲過程中的嗲嗎訪問三個表和四個視圖,SQL Server在執行代碼前進行這些概念上的步驟:
- 在代碼上驗證調用者有EXECTUTE許可。如果調用者沒有,返回一個錯誤並不再繼續。
- 檢查代碼的所有者是否也有代碼訪問對象的許可。如果共同所有權(common ownership)存在的話,停止檢查並執行代碼。
- 如果共同所有權(common ownership)不存在的話,檢查確保調用者代碼訪問的對象上有許可。如果調用者在一個或多個對象上沒有許可,返回一個錯誤且不執行代碼。
- 如果調用者有所有需要的許可,執行代碼。否則,返回錯誤且不執行代碼。
在代碼調用其它帶按摩或訪問其它對象的地方,共同所有權繼續檢查,即輪流調用其它代碼或訪問其它對象。只要鏈里的所有對象有同樣的所有者,許可檢查就不需要。但只要鏈里的一個對象比要訪問它的對象多出不同的所有者,在那個對象上的許可就檢查了。
在這鐘情形里的對象所有權被稱為所有權鏈接接(ownership chain),因為你不需要擔心代碼執行的安全上下文。這也是SQL Server的早期版本有擁有所有對象的特定dbo角色的直接原因。但任何時候你有共同所有權和許可來訪問一切,你就違法了最小特權許可,暴露你的數據在不需要的安全危機里。
幸運的是,在SQL Server里你可以修改代碼的安全執行上下文。
提示:
這篇文章會探尋執行在存儲過程上的執行上下文和代碼簽名,但它們同樣對大多數用戶自定義函數也支持。
修改執行上下文
一般你不想調用者的許可用來在破壞的所有權鏈接接里驗證許可。有時你想代碼好像完全被另一個用戶執行一樣,通過另一個用戶的許可在訪問的所有的對象上驗證許可。這稱為切換代碼的執行上下文。這讓你使用SQL Server顆粒度許可的優點,對潛在的對象保持完全的許可控制,但還是給不同用戶執行代碼的能力。
在SQL Server里,當你定義任何類型的用戶自定義函數(行內表值函數除外),存儲過程和觸發器,你可以使用EXECUTE AS子句作為對象定義的一部分,表示這個代碼應該在指定用戶的安全上下文下運行。
EXECUTE AS有4個可用選項:
- EXECUTE AS CALLER:預設用戶向下相容。代碼在調用者的上下文里執行,調用者必須同時有執行代碼和訪問潛在對象的許可。實際的操作取決於所有權鏈接接上是否損壞或完好。
- EXECUTE AS = ‘username’ and EXECUTE AS = ‘loginname’:代碼在指定用戶或登錄的上下文里運行,因此指定的用戶或登錄必須在所有的潛在對象上有許可。在這個情況下,調用者必須滿足下列之一:
- 在代碼上有EXECUTE許可
- 是sysadmin或db_owner,或者在伺服器或資料庫上有CONTROL SERVER許可,或者對於用戶有模仿(impersonate)許可。
使用用戶名的EXECUTE AS只能應用於伺服器範圍的DDL觸發器,且要登錄到觸發器。否則,提供的用戶名必須是有效的資料庫用戶名稱。
- EXECUTE AS SELF:這是創建存儲過程的當前用戶的縮寫。和EXECUTE = [myUserName]等效。SQL Server目錄村裡寫代碼的實際用戶ID。
- EXECUTE AS OWNER:這是在指定用戶的安全上下文運行的另一個變體,在這個情況下,代碼所有者在代碼執行時間,而不是在創建時間。如果在資料庫里,擁有者在代碼創建後修改了,這表示代碼會在和首次創建代碼的不同用戶的許可執行。
當你在SSMS里運行代碼時,在會話的執行上下文里,有兩種EXECUTE AS的變體可以作為語句使用。它們是EXECUTE AS LOGIN = ‘loginname’ 和EXECUTE AS USER = ‘username’。當用戶登錄到SQL Server實例時,會話開始,那個時候的執行上下文設置為登錄的用戶,用作許可檢查。EXECUTE AS 修改會話期間執行上下文,直到用戶執行了REVERT語句。
通過EXECUTE AS修改安全上下文的任何時間,代碼創建者或會話用戶在語句里指定的用戶必須有模仿(impersonate)許可。你永遠不需要模擬自己的許可,例如EXECUTE AS SELF。
使用EXECUTE AS子句
在資料庫里,假設你有Vendor表。表在SchemaUserTable架構里定義,屬於UserTable用戶。代碼6.1定義了範文這個表的存儲過程。在SchemaUserProc定義的存儲過程,屬於UserProc用戶。因為表和存儲過程在屬於不同用戶的不同架構里定義,存在斷開的所有權鏈接接。
1 USE ExecuteContextDB; 2 GO 3 CREATE PROC SchemaUserProc.VendorAccessProc @state CHAR(2) 4 AS 5 SELECT * FROM SchemaUserTable.Vendor WHERE state = @state; 6 GO
代碼6.1:創建在一個在一個架構里訪問不同架構里的表的存儲過程,這裡架構有不同的擁有者。
提示:
下麵的代碼會創建登錄、資料庫、用戶和這個部分使用的架構,同樣也會在Vendor表裡插入一些記錄。先運行下列帶代碼再運行代碼6.1。
1 -- Create the logins and database for this demo 2 USE master; 3 GO 4 5 IF SUSER_SID('UserProc') IS NOT NULL DROP LOGIN UserProc; 6 IF SUSER_SID('UserTable') IS NOT NULL DROP LOGIN UserTable; 7 IF SUSER_SID('RealUser') IS NOT NULL DROP LOGIN RealUser; 8 GO 9 CREATE LOGIN UserProc WITH password = 'Y&2!@37z#F!l1zB'; 10 CREATE LOGIN UserTable WITH password = 'Y&2!@37z#F!l1zB'; 11 CREATE LOGIN RealUser WITH password = 'Y&2!@37z#F!l1zB'; 12 GO 13 14 IF DB_ID('ExecuteContextDB') IS NOT NULL DROP DATABASE ExecuteContextDB; 15 CREATE DATABASE ExecuteContextDB; 16 GO 17 USE ExecuteContextDB; 18 GO 19 20 -- Create the users 21 CREATE USER UserProc; 22 CREATE USER UserTable; 23 CREATE USER RealUser; 24 GO 25 26 -- Create the schemas 27 CREATE SCHEMA SchemaUserProc AUTHORIZATION UserProc; 28 GO 29 CREATE SCHEMA SchemaUserTable AUTHORIZATION UserTable; 30 GO 31 32 -- Create a table and a proc in different schemas to ensure that 33 -- there is no ownerhship chaining. 34 CREATE TABLE SchemaUserTable.Vendor 35 (ID INT, name VARCHAR(50), state CHAR(2), phno CHAR(12)); 36 GO 37 SET NOCOUNT ON 38 GO 39 INSERT INTO SchemaUserTable.Vendor VALUES (1,'Vendor1','AK','123-345-1232'); 40 INSERT INTO SchemaUserTable.Vendor VALUES (2,'Vendor2','WA','454-765-3233'); 41 INSERT INTO SchemaUserTable.Vendor VALUES (3,'Vendor3','OR','345-776-3433'); 42 INSERT INTO SchemaUserTable.Vendor VALUES (4,'Vendor4','AK','232-454-5654'); 43 INSERT INTO SchemaUserTable.Vendor VALUES (5,'Vendor5','OR','454-545-5654'); 44 INSERT INTO SchemaUserTable.Vendor VALUES (6,'Vendor6','HI','232-655-1232'); 45 INSERT INTO SchemaUserTable.Vendor VALUES (7,'Vendor7','HI','453-454-1232'); 46 INSERT INTO SchemaUserTable.Vendor VALUES (8,'Vendor8','WA','555-654-1232'); 47 INSERT INTO SchemaUserTable.Vendor VALUES (9,'Vendor9','AK','555-345-1232'); 48 GO
代碼6.2在存儲過程上授予EXECUTE許可給真正的用戶,RealUser,它會運行代碼。
1 -- Grant permissions on the stored procedure 2 GRANT EXECUTE ON SchemaUserProc.VendorAccessProc TO RealUser; 3 GO
代碼6.2:在新的存儲過程上授予EXECUTE許可。
在SSMS里,你可以運行EXECUTE AS作為臨時修改安全上下文的在查詢窗體里運行的代碼語句。使用代碼6.3修改安全上下文為RealUser來運行存儲過程獲得在位於阿拉斯加的供應商列表。
1 EXECUTE AS user = 'RealUser'; 2 EXEC SchemaUserProc.VendorAccessProc 'AK';
代碼6.3:修改執行上下文,以RealUser運行存儲過程。
執行這個代碼引起了下列錯誤:
在對象Vendor上,SELECT許可被拒絕,資料庫 'ExecuteContextDB',架構 'SchemaUserTable'。
問題是所有權鏈接接斷開了——存儲過程的所有者和表的所有者不同——RealUser在Vendor表上沒有SELECT許可。這裡有SQL Server如何從概念上分析情況:
- 調用者是RealUser,它有EXECUTE許可。通過!
- 存儲過程的所有者是UserProc。表的所有者是UserTable。這表示有斷開的所有權鏈接接,因此檢查調用者RealUser,在代碼里有進行操作的許可。
- RealUser在Vendor表上沒有SELECT許可,拋出錯誤。失敗!
你可以在存儲過程定義里使用EXECUTE AS子句來修正問題,假設你是存儲過程的創建者,在這個情況里想允許RealUser運行代碼。首先,使用代碼6.1顯示的REVERT語句來撤銷安全上下文的RealUser切換,返回你自己的安全上下文:
1 REVERT;
代碼6.4:恢復用戶運行SSMS的原始安全上下文
接下來,修改存儲過程來包含EXECUTE AS子句使用UserTable的安全上下文來運行存儲過程,它在表上擁有SELECT許可,如代碼6.5所示。
1 ALTER PROC SchemaUserProc.VendorAccessProc @state CHAR(2) 2 WITH EXECUTE AS 'UserTable' 3 AS 4 SELECT * FROM SchemaUserTable.Vendor WHERE state = @state; 5 GO
代碼6.5:修改存儲過程,在運行時間使用EXECUTE AS修改運行上下文
提示:
在這個例子里,UserTable通過成員資格擁有在Vendor表上的SELECT許可。但成員資格沒有必要用來執行上下文切換工作。例如,可以通過表擁有者授予用戶EXECUTE AS user許可。
然後修改安全上下文為UserTable,再次嘗試運行存儲過程,使用代碼6.6。
1 EXECUTE AS user = 'RealUser'; 2 EXEC SchemaUserProc.VendorAccessProc 'AK'; 3 REVERT;
代碼6.6:測試修改後的存儲過程來看看RealUser現在能否執行代碼。
這次調用成功,因為當SQL Server在所有權鏈接接檢查許可時——還是斷開的——它發現UserTable有需要的SELECT許可。結果如插圖6.1所示。
插圖6.1:在不同的用戶安全上下文下運行存儲過程的結果
代碼簽名
使用EXECUTE AS子句修改T-SQL代碼段的安全上下文只是解決斷開所有權鏈接接問題的一種方法。另一個方法是使用證書或非對稱匙的簽名代碼。這個技術授予代碼本身許可,不需要你修改運行上下文或取決於調用者的許可。而且通過證書的使用或非對稱匙的嚴格控制,你還是可以控制哪個主體能利用運行代碼的許可。
這個方式的方法是你創建一個安全,加密的證書或非對稱匙,然後創建與證書或匙關聯的用戶。這是特別的用戶類型,不關聯登錄。你分配需要的許可來運行到用戶的存儲過程,然後使用ADD SIGNATURE語句分配證書或匙到存儲過程。存儲過程使用證書或匙關聯的用戶許可。
即使存儲過程使用EXECUTE AS語句修改執行上下文,你還是可以使用這個技術。代碼簽名的常見情形是,修改執行上下文到代碼需要執行的最多許可的運行上下文,然後使用代碼簽名添加一個或多個額外許可。
和往常一樣,需要通過實例來演示下這個技術。代碼6.7創建再次從ExecuteContextDB資料庫的Vendor表獲取數據。UnsignedProc存儲過程沒有簽名,因此當RealUser運行它的時候會失敗。SignedProc存儲過程會簽名,對於RealUser用戶會正常運行。
1 CREATE PROC SchemaUserProc.UnsignedProc @state CHAR(2) 2 AS 3 SELECT * FROM SchemaUserTable.Vendor WHERE state = @state; 4 GO 5 CREATE PROC SchemaUserProc.SignedProc @state CHAR(2) 6 AS 7 SELECT * FROM SchemaUserTable.Vendor WHERE state = @state; 8 GO 9 10 GRANT EXECUTE ON SchemaUserProc.UnsignedProc TO RealUser; 11 GRANT EXECUTE ON SchemaUserProc.SignedProc TO RealUser; 12 GO
代碼6.7:創建唯一的存儲過程並對RealUser在它們上面授予EXECUTE許可
但這次,我們不是通過修改運行上下文授予SELECT許可,我們會創建一個證書,如代碼6.8所示。代碼然後創建從證書創建一個用戶,對用戶在Vendor表上授予SELECT許可。最後,代碼使用ADD SIGNATURE語句添加證書到SignedProc存儲過程。註意只有SignedProc拿到簽名;UnsignedProc還是沒簽名。
1 CREATE CERTIFICATE MyCertificate 2 ENCRYPTION BY PASSWORD = 'SZ6T4O^ff&1Kr3s?m\*' 3 WITH SUBJECT = 'Certificate to sign SignedProc'; 4 GO 5 6 CREATE USER MyCertificateUser 7 FROM CERTIFICATE MyCertificate; 8 9 GRANT SELECT ON SchemaUserTable.Vendor TO MyCertificateUser; 10 GO 11 12 ADD SIGNATURE TO SchemaUserProc.SignedProc BY CERTIFICATE MyCertificate 13 WITH PASSWORD = 'SZ6T4O^ff&1Kr3s?m\*'; 14 GO
代碼6.8:實現證書和分配許可到代碼的代碼
最後,到測試簽名架構代碼的時候了,如代碼6.9所示。結果如插圖6.2所示,UnsignedProc有斷開的所有權鏈接接,RealUser在Vendor表上沒有SELECT許可,因此執行失敗。SignedProc通過使用代碼簽名授予SELECT許可,成功執行返回三條阿拉斯加的數據。
1 EXECUTE AS USER = 'RealUser'; 2 3 -- Can't run UnsignedProc 4 EXEC SchemaUserProc.UnsignedProc 'AK'; 5 -- Can run SignedProc 6 EXEC SchemaUserProc.SignedProc 'AK'; 7 8 REVERT;
代碼6.9:測試使用證書簽名的代碼
插圖6.2:測試在UnsignedProc和SignedProc存儲過程代碼簽名的結果
配置這個有點複雜,但安全上受益非常值得。使用正確的話,這個技術剔除了用戶在潛在對象上需要的SELECT許可和存儲過程上的EXECUTE許可。很可能這不是你在存儲過程或用戶自定義函數里廣泛使用的,但當處理斷開的所有權鏈接接且沒有方便的擁有需要許可的主體時,它漂亮的解決了一些安全問題。
小結
在SQL Server里創建存儲過程和用戶自定義函數的最簡單方法是用完整的所有權鏈接接來是實現它們,代碼的所有者也擁有代碼訪問的所有資料庫對象。但通常這並不可行,當在SQL Server實例里對象的成員資格在多個主體間分發的時候。這篇文件介紹了你可以處理斷開的所有權鏈接接的兩個技術,通過修改執行上下文和使用代碼簽名分配許可。這些技術是免費贈送的,因此你可以對單個存儲過程或函數一起使用它們。那樣的話,你可以處理你面對的任何許可架構,來保持你的資料庫及它的資料庫儘可能的安全。
原文鏈接
http://www.sqlservercentral.com/articles/Stairway+Series/121476/