譯自K. Brian Kelley 的博文
我已經掌握了所有權鏈,當我想知道跨數據庫的所有權鏈是怎樣的?其是如何工作的?如果擁有者是基於數據庫的用戶的,那麼跨數據庫的擁有者如何確定?
跨數據庫所有權鏈,是所有權鏈的一個擴展,除非其確實跨越了數據庫的邊界。如果你不熟悉所有權鏈,你應該從這篇《所有權鏈在SQL Server安全特性或安全風險》早期文章開始。跨數據庫所有權鏈可能發生的一個實例是,如果你在一個數據庫中有一個視圖引用了另外一個數據庫的一個表。視圖在第一個數據庫中,引用了第二個數據庫中的表。如果我們談論的是在相同數據庫中的對象,如果表和視圖的擁有者都是相同的用戶,將會形成所有權鏈,終端用戶將僅需要視圖的訪問權限。在跨數據庫所有權鏈中,除跨數據庫不同外,其他可能都一樣。
跨數據庫所有權鏈既可以在實例級別開啓,也可以在數據級別開啓。如果跨數據庫所有權鏈在實例級開啓,那麼對於實例上的所有數據庫將都開啓跨數據庫所有權鏈,不管個別數據庫的設置是怎樣的。默認情況下,實例級跨數據庫所有權鏈是關閉的,除下面三個數據庫外,其他數據庫也是關閉的:
-
master
-
msdb
-
tempdb
這三個系統數據庫需要跨數據庫所有權鏈處於開啓狀態。這三個之外,一般規則,因爲安全的影響,應該是:
-
實例級跨數據庫所有權鏈不應該開啓
-
僅僅當需要時,纔可以開啓數據級跨數據庫所有權鏈。
你可以通過如下語句(SQL Server 2005及以上版本),確定是否開啓實例級跨數據庫所有權鏈。如果value值是0,實例級是關閉的;1則表示開啓狀態。
SELECT
name, value
FROM sys.configurations
WHERE name = 'cross db ownership chaining'
這也適用於sys.databases 中的is_db_chaining_on 列。我們可以通過查詢sys.databases 來查看哪些數據庫開啓了跨數據庫所有權鏈:
SELECT
name AS DBName
,is_db_chaining_on
FROM sys.databases
ORDER BY name
對於開啓跨數據庫所有權鏈的數據庫,允許在數據層形成所有權鏈。所有權鏈的確定方式和數據庫中的所有權鏈相似。不同的是,如果可能的話,每個對象的擁有者最終都映射爲同一個對象(如果必須形成跨數據庫所有權鏈)。
在SQL Server 2005及以上版本中,可能創建一個沒有登錄賬戶的用戶。爲確定這些映射,下面的查詢將展示存儲過程和用戶表的最終擁有者:
SELECT
so.name AS ObjectName
,sch.name AS SchemaName
,sp.name AS LoginName
,USER_NAME(COALESCE(so.principal_id,sch.principal_id)) AS OwnerUserName
,so.type_desc AS ObjectType
FROM sys.objects so
JOIN sys.schemas sch
ON so.[schema_id]=sch.[schema_id]
JOIN sys.database_principals dp
ON dp.principal_id=COALESCE(so.principal_id,sch.principal_id)
LEFT JOIN master.sys.server_principals sp
ON dp.sid=sp.sid
WHERE so.[type] IN ('U','P');
因此,如果在一個數據庫中有對象訪問第二個數據庫中的一個對象,兩個數據庫都配置啓用了數據庫所有權鏈(或者在實例級配置),並且兩個對象擁有者相同,那麼跨數據庫所有權鏈將形成。和普通的所有權鏈相同,將在檢查第一個對象的權限,而不是第二個。然而,有一個問題使它不同於正常的所有權鏈。查詢一個對象的登陸賬戶,必須能訪問第二個數據庫。在master、msdb或tempdb中,將由guest用戶完成。但是如果登陸賬戶沒有連接第二個數據庫的權限,查詢將會失敗。
下面展示一個不同配置表(假設兩個數據庫都開啓跨數據庫所有權鏈):
Access to 1st DB | Access to 2nd DB | Guest User Enabled on 2nd DB? | Cross Database Ownership Forms? |
Yes | No | No | No |
Yes | No | Yes | Yes |
Yes | Yes | No | Yes |
Yes | Yes | Yes | Yes |
如果跨數據庫所有權鏈不能形成,那麼,如果一個對象引用和該對象所在數據庫不同的另一個數據庫的對象,登陸賬戶必需映射到每個數據庫,並且擁有對象合適的權限。
下面是一個例子幫助說明這個問題
USE MASTER;
GO
-- 創建測試數據
CREATE DATABASE Original_Database;
GO
CREATE DATABASE Chained_Database;
GO
USE Chained_Database;
GO
--修改數據庫所有者
EXEC sp_changedbowner 'sa';
GO
CREATE TABLE dbo.ATable (TableID INT);
GO
USE Original_Database;
GO
EXEC sp_changedbowner 'sa';
GO
--創建數據庫角色
EXEC sp_addrole 'All_Users';
GO
CREATE PROC dbo.QueryATable
AS
BEGIN
SELECT TableID FROM Chained_Database.dbo.ATable;
END;
GO
GRANT EXECUTE ON dbo.QueryATable TO All_Users;
GO
--創建登陸名
EXEC sp_addlogin 'Standard_Login', 'SomeStrongP4ssword!';
GO
USE Original_Database;
GO
--創建用戶
EXEC sp_grantdbaccess 'Standard_Login';
GO
EXEC sp_addrolemember @membername = 'Standard_Login', @rolename = 'All_Users';
GO
USE Chained_Database;
GO
EXEC sp_grantdbaccess 'Standard_Login';
GO
-- Attempt to use QueryATable without Cross-Database Ownership Chaining
-- For SQL Server 2000, log in as the created login and attempt to execute the stored procedure
USE Original_Database;
GO
EXECUTE AS LOGIN = 'Standard_Login';
GO
EXEC dbo.QueryATable;
GO
REVERT;
GO
-- 開啓誇庫所有權鏈
EXEC sp_dboption 'Original_Database', 'db_chaining', 'true';
GO
EXEC sp_dboption 'Chained_Database', 'db_chaining', 'true';
GO
-- Attempt to use QueryATable after Cross-Database Ownership Chaining has been enabled
-- For SQL Server 2000, log in as the created login and attempt to execute the stored procedure
USE Original_Database;
GO
EXECUTE AS LOGIN = 'Standard_Login';
GO
EXEC dbo.QueryATable;
GO
REVERT;
GO
-- Clean-up
USE MASTER;
GO
DROP DATABASE Original_Database;
GO
DROP DATABASE Chained_Database;
GO
EXEC sp_droplogin 'Standard_Login';
GO