【譯】第七篇 SQL Server安全跨數據庫所有權鏈接


本篇文章是SQL Server安全系列的第七篇,詳細內容請參考原文


Relational databases are used in an amazing variety of applications with connections from a dizzying array of clients over widely distributed networks,特別是互聯網,使得數據幾乎向任何人,任何地方開放。數據庫可以包含相當大部分的人類知識,包括高度敏感的個人信息和關鍵數據。
數據庫的這些特性使得有人想要竊取數據或通過篡改以損害它的主人。確保你的數據是安全的是一個關鍵部分來配置SQL Server和開發應用程序存儲數據。這一系列將探索SQL Server 2012安全基礎,那樣你可以保護你的數據和服務器資源,getting as granular as you need to be to protect against the unique security threats that can affect your data.大部分的功能適用於早期版本的SQL Server,但我也會討論只適用於SQL Server 2012和以后版本的功能。
大多數的時候,你可能會關心獨立數據庫中有單獨所有者的數據和對象訪問的安全。但有時需要從多個數據庫中訪問數據和對象,這會引起一些安全問題,增加數據訪問的復雜性。在這一篇,你會學習跨數據庫所有權鏈接,使你可以安全的跨越數據庫邊界。
所有權鏈接
大多數的時候,你創建的數據庫對象所引用的其他對象都是在同一個數據庫中。比如一個存儲過程所訪問的表在同一個數據庫,一個視圖所連接的表都在同一個數據庫。但是,有時你需要創建對象訪問其他數據庫中的對象。在以前,跨數據庫訪問的安全規則遵循在單一數據庫中的規則。訪問用戶需要對訪問對象有必要的權限,連續的所有權鏈接允許SQL Server簡化權限檢查。畢竟,SQL Server不能放松警惕無論對象存儲在哪。類似單一數據庫中,所有權鏈接有助於簡化跨數據庫的安全管理。
所有權鏈接工作的基本原理是一樣的,不管你是在一個單一的數據庫還是跨數據庫。所有數據庫對象都有一個所有者,並且所有者控制誰在對象上有權限。對象訪問其他對象,比如存儲過程在SELECT語句把多張表聯接起來,形成一個連續的所有權鏈接,只要一個所有者擁有所有對象。
這種情況下用戶只要有頂層對象(比如存儲過程、視圖)的權限,訪問其他對象並不需要對底層對象有權限,只要存在一個連續的所有權鏈接就行。SQL Server一旦證實用戶對頂層對象有權限就會停止檢查底層對象的權限。這種設計為用戶提供了更多更好的控制權,因為用戶只需要直接訪問對象的權限。
提示:所有權鏈接只適用於對象的權限,如SELECT、UPDATE和EXECUTE操作。SQL Server總是檢查數據定義語言語句的權限,因為這些權限應用於語句而不是對象。
跨數據庫所有權鏈接
跨數據庫所有權鏈接是所有權鏈接的一個擴充,所有權鏈接的所有對象(包括用戶直接訪問的對象以及底層相關的對象)在同一個數據庫。唯一不同的是跨數據庫所有權鏈接跨越數據庫邊界。因此你可以在數據庫中有一個視圖將多個數據庫下的表聯接在一起。或者是一個存儲過程訪問多個數據庫下的對象。在這些情況下,用戶直接訪問的源對象依賴於另一個數據庫中的對象。
這兩種所有權鏈的唯一重要的區別是,主體可以跨庫成為其他對象的所有者。數據庫用戶也是一個主體,是完全包含在一個單一數據庫中。即使多個數據庫中都具有相同名稱的數據庫用戶,這些都是獨立的,不同的主體,不能參與一個完整的所有權鏈接,除非所有這些用戶都映射到相同的服務器級別登錄名。所以相關的所有者是登錄名,而不是數據庫用戶。一個關鍵的概念:一個連續的所有權鏈接中的對象的共同擁有者是一個服務器級的主體,而不是一個數據庫級的主體。
SQL Server內部通過SID而不是名稱來區別對象的所有者。在一個單一的數據庫,all objects owned by a single user have a single SID specified as the owner,因為在同一數據庫中用戶名不能重復。但在跨數據庫中,SID是終極所有者在服務器級別的登錄名對應。不同的用戶在不同的數據庫下可以有不同的登錄名,所以會有不同的SID。這可能是用戶與登錄名潛在容易混亂的方面,所以要確保你清楚這一點!
一個連續的所有權鏈接要求所有對象(源對象和關聯對象)的所有者映射到相同的登錄名及相同的SID。
探索跨數據庫所有權鏈接
本篇的樣例代碼會說明如何使用跨數據庫所有權鏈接並解釋它的特性。代碼首先會在服務器級別創建一個叫做SharedLogin登錄名。之后的代碼會使用此登錄名作為所有權鏈接中對象的共享所有者。

USE master;
GO
IF SUSER_SID('SharedLogin') IS NOT NULL DROP LOGIN SharedLogin;
CREATE LOGIN SharedLogin WITH password = 'Y&2!@37z#F!l1zB';
GO

代碼7.1 創建SharedLogin登錄名
我們需要兩個數據庫來演示跨數據庫所有權鏈接,因此接下來代碼會創建它們。用戶直接訪問對象的數據庫叫做SourceDB,關聯對象的數據庫叫做ChainedDB。ChainedDB包含一個dbo.AlaskaCity表,里面有Alaska三大城市的人口數據。代碼7.2會創建ChainedDB庫和AlaskaCity表,然后往表中插入部分數據:

IF DB_ID('ChainedDB') IS NOT NULL DROP DATABASE ChainedDB;
CREATE DATABASE ChainedDB;
GO
USE ChainedDB;
GO
-- Create a table for access from another database
CREATE TABLE dbo.AlaskaCity
(
    AlaskaCityID INT NOT NULL IDENTITY(1, 1), 
    CityName NVARCHAR(20) NOT NULL, 
    Population INT NOT NULL
    CONSTRAINT PK_AlaskaCity PRIMARY KEY (AlaskaCityID)
);
GO
INSERT INTO dbo.AlaskaCity (CityName, Population)
    VALUES ('Fairbanks', 31535), ('Anchorage', 291826), ('Juneau', 31275);
GO

代碼7.2 創建ChainedDB庫和AlaskaCity表並插入測試數據
代碼7.3創建SourceDB庫和一個視圖,用戶直接訪問視圖以返回ChainedDB庫AlaskaCity表中的數據。代碼中包含一個查詢語句測試視圖返回數據是否正確。如果你是以sysadmin身份登錄這個語句應該正常執行。作為管理員運行是很方便,但是在生產使用它不安全,通常一個用戶訪問的代碼不會有所有對象的所有權!

IF DB_ID('SourceDB') IS NOT NULL DROP DATABASE SourceDB;
CREATE DATABASE SourceDB;
GO
USE SourceDB;
GO
-- Create a view that accesses ChainedDB.dbo.AlaskaCity
CREATE VIEW dbo.AlaskaCitiesView AS
    SELECT * FROM ChainedDB.dbo.AlaskaCity;
GO
SELECT * FROM dbo.AlaskaCitiesView ORDER BY Population DESC;

代碼7.3 創建SourceDB庫和AlaskaCitiesView視圖
現在你有兩個數據庫,其中一個庫下的對象會訪問另一庫下的對象,而且在sysadmin下執行是正常。代碼7.4在SourceDB庫創建一個SourceUser用戶映射到SharedLogin登錄名,並且將AlaskaCitiesView視圖的查詢權限授予給SourceUser用戶。然后代碼更改執行上下文到SharedLogin並嘗試訪問AlaskaCitiesView視圖。查詢會成功嗎?

USE SourceDB;
GO
-- Create a user in the SourceDB who will access the view
CREATE USER SourceUser FOR LOGIN SharedLogin;
GRANT SELECT ON dbo.AlaskaCitiesView TO SourceUser;
GO
-- Try accessing the view as SourceUser
EXECUTE AS LOGIN = 'SharedLogin';
SELECT * FROM dbo.AlaskaCitiesView ORDER BY Population DESC;
GO
REVERT;

代碼7.4 創建一個SourceUser用戶映射到SharedLogin登錄名
查詢拋出一個錯誤:服務器主體 "SharedLogin" 無法在當前安全上下文下訪問數據庫 "ChainedDB"。
我們有一個連續的所有權鏈接:視圖和表共享相同的所有者——dbo數據庫用戶。但是在服務器實例或新數據庫上沒有啟用跨數據庫所有權鏈接,下面我們將啟用它。
啟用跨數據庫所有權鏈接
新安裝的SQL Server實例跨數據庫所有權鏈接選項默認是關閉的。這是因為啟用這個選項會在實例的安全盔甲上打開幾個漏洞;我將在這篇后面簡要地講解風險。當選項是禁用的,當代碼依賴跨數據庫所有權鏈接就會生成權限拒絕錯誤。(正如你稍后會看到的,在代碼執行前這不是唯一需要修正的問題,但它是第一個我們將解決。)
服務器級別啟用
在服務器級別為所有數據庫啟用跨數據庫所有權鏈接。如果你啟用它,它會應用到所有數據庫上而且你不能在數據庫上限制它。
使用SSMS,對象資源管理器下右擊實例->屬性->安全性,在對話框的底部你會看到跨數據庫所有權鏈接的選項,如圖7.1所示,點擊確定實例下的所有數據庫就會啟用。

圖7.1 啟用跨數據庫所有權鏈接
你也可以用T-SQL語句啟用,如代碼7.5所示。比如在其他服務器實例將"cross db ownership chaining "值設為1啟用,O禁用。確保使用RECONFIGURE語句而不需要重啟實例才能讓更改生效。一旦你執行了這段代碼,你就可以在SQL Server實例的任何數據庫中使用跨數據庫所有權鏈接。

USE master;
GO
EXECUTE sp_configure 'cross db ownership chaining', 1;
RECONFIGURE;

代碼7.5 使用T-SQL啟用跨數據庫所有權鏈接
如果你使用SSMS或T-SQL已啟用了跨數據庫所有權鏈接,請將它關閉。這不是啟用它的最好方法,除非你真的打算在實例的每個數據庫下從源對象訪問關聯對象。如果不打算這樣,在實例上啟用太不安全,而且不是好主意。
作為替代,你應該針對需要的數據庫啟用跨數據庫所有權鏈接。
數據庫級別啟用
在服務器級別禁用了跨數據庫所有權鏈接,如果你需要使用的話,你必須在數據庫級別啟用它。如果一個數據庫禁用了,它就不能作為源或鏈接數據庫參與跨數據庫所有權鏈接。為了讓鏈接工作,在源和鏈接數據庫都必須設置啟用。
默認當你創建或附加用戶數據庫時跨數據庫所有權鏈接是禁用的。但是master、msdb和tempdb系統數據庫是啟用的,model數據庫是禁用的。因為SQL Server用戶內部使用跨數據庫所有權鏈接,我們不能禁用或啟用系統數據的選項。
提示:如果你分離然后再附加一個已經啟用跨數據庫所有權鏈接的數據庫,你必須在數據庫附加后重新啟用選項。這不適合於服務器級別已啟用的,服務器級別會自動應用到所有數據庫上。
不幸地是,在SSMS->數據庫屬性->選項,"跨數據庫所有權鏈接已啟用"選項是False,如圖7.2所示。為了更改數據庫的這個值,你需要使用代碼7.6,啟用SourceDB和ChainedDB數據庫的設置。

圖7.2 查看ChainedDB數據庫跨數據庫所有權鏈接已啟用選項

ALTER DATABASE ChainedDB SET DB_CHAINING ON;
ALTER DATABASE SourceDB SET DB_CHAINING ON;

代碼7.6 啟用跨數據庫所有權鏈接
兩個數據庫都啟用跨數據庫所有權鏈接后,你可以嘗試使用SharedLogin安全上下文再次訪問視圖:

USE SourceDB;
GO
EXECUTE AS LOGIN = 'SharedLogin';
SELECT * FROM dbo.AlaskaCitiesView ORDER BY Population DESC;
GO
REVERT;

代碼7.7 嘗試使用跨數據庫所有權鏈接跨過數據庫邊界訪問對象
還是拋出同樣的錯誤:服務器主體 "SharedLogin" 無法在當前安全上下文下訪問數據庫 "ChainedDB"。這次的問題是要滿足一個需求。用戶訪問視圖必須也能夠訪問ChainedDB數據庫,用戶不需底層對象的權限。事實上,用戶不需要在那個數據庫下的任何權限。
代碼7.8展示了如何解決這個問題。在ChainedDB庫創建一個ChainedUser用戶映射到SharedLogin登錄名。這給SharedLogin在ChainedDB庫一個立足點,跨數據庫鏈接工作的必要條件。為了驗證SharedLogin在AlaskaCity表上沒有查詢權限,代碼嘗試直接從AlaskaCity表上讀取數據。但是嘗試失敗,因為SharedLogin和ChainedUser在ChainedDB庫沒有任何權限。這次的錯誤是:拒絕了對對象 'AlaskaCity' (數據庫 'ChainedDB',架構 'dbo')的 SELECT 權限。

USE ChainedDB;
GO
CREATE USER ChainedUser FOR LOGIN SharedLogin;
-- Note that we're not granting the user any permissions in the chained database.
GO
-- Verify that SharedLogin doesn't have direct access to the AlaskaCity table, even in the ChainedDB database context.
EXECUTE AS LOGIN = 'SharedLogin';
SELECT * FROM dbo.AlaskaCity;
GO
REVERT;

代碼7.8 ChainedDB庫創建一個ChainedUser用戶映射到SharedLogin登錄名
現在你可以返回執行代碼7.7,它會成功並返回視圖中的數據!
使用代碼7.9禁用數據庫所有權鏈接——實際上你只需要執行代碼中任何一條語句,因為要讓跨數據庫所有權鏈接正常工作兩個數據庫的都要設置成ON。然后你再運行代碼7.7,此時對象所有者相同且用戶能訪問兩個數據庫。這次報錯:拒絕了對對象 'AlaskaCity' (數據庫 'ChainedDB',架構 'dbo')的 SELECT 權限。

ALTER DATABASE ChainedDB SET DB_CHAINING OFF;
ALTER DATABASE SourceDB SET DB_CHAINING OFF;

代碼7.9 禁用數據庫所有權鏈接
共用所有權
樣例代碼中的視圖在啟用跨數據庫所有權鏈接后能正常執行的原因是:視圖和表共用所有權。樣例代碼假定是以sysadmin登錄到SSMS編寫的,因此代碼所創建的對象都被dbo用戶所擁有,用戶都是映射到sysadmin。你可以用代碼7.10驗證映射關系,顯示架構名稱、所有者用戶名、所有者登錄名。如果你在SourceDB和ChainedDB庫下運行,你會發現所有者登錄名是相同的,如圖7.3和7.4所示,在我的例子中,dbo用戶映射到登錄名USER-67NP5R8LGK\Administrator.

SELECT
    so.[name] AS Object, 
    sc.[name] AS [Schema], 
    USER_NAME(COALESCE(so.principal_id, sc.principal_id)) AS OwnerUserName, 
    sp.name AS OwnerLoginName, 
    so.type_desc AS ObjectType 
FROM sys.objects so 
    JOIN sys.schemas sc ON so.schema_id = sc.schema_id 
    JOIN sys.database_principals dp ON dp.principal_id = COALESCE(so.principal_id, sc.principal_id)
    LEFT JOIN master.sys.server_principals sp ON dp.sid = sp.sid
WHERE so.[type] IN ('U', 'V'); 

代碼7.10 顯示對象架構名稱、所有者用戶名、所有者登錄名

圖7.3 SourceDB庫下運行結果

圖7.4 ChainedDB庫下運行結果
要注意的是一個數據庫的dbo用戶不一定和另一個數據庫下的dbo用戶相同。它們可以被映射到完全不同的登錄名或者不映射。重要的是鏈接中所有對象的擁有者有相同的SID。你可以用代碼7.11驗證對象所有者和登錄名有相同的SID。

SELECT name, sid FROM SourceDB.sys.database_principals WHERE name = 'dbo'
UNION ALL
SELECT name, sid FROM ChainedDB.sys.database_principals WHERE name = 'dbo'
UNION ALL
SELECT 'USER-67NP5R8LGK\Administrator', SUSER_SID('USER-67NP5R8LGK\Administrator');

代碼7.11 驗證對象所有者和登錄名有相同的SID

圖7.5 SourceDB和ChainedDB庫下dbo用戶,及代碼會話登錄用戶的SID
跨數據庫所有權鏈接的風險
啟用跨數據庫所有權鏈接存在風險因素的原因是高特權用戶可能濫用。微軟以2種方式來描述風險,這兩者都涉及到數據庫的安全邊界的潛在風險:
->在一個數據庫中,數據庫所有者和db_ddladmin和db_owners數據庫角色的成員可以創建其他用戶所擁有的對象。這些新對象能夠使用其他數據庫下具有相同所有者的對象,這樣無意中就可以訪問其他數據庫下的對象。
->具有CREATE DATABASE權限的數據庫用戶可以創建新數據庫和附加數據庫。啟用跨數據庫所有權鏈接,這些用戶可以從新創建的或附加的數據庫訪問其他數據庫對象。
這里的關鍵是,你要相信高特權用戶,那些權限超越了簡單的訪問和在特定表的維護數據,如果你要允許跨數據庫所有權鏈接。這就是為什么微軟強烈建議,如果你需要啟用跨數據庫所有權鏈接,只在需要的數據庫上啟用它,為了遏制安全風險。

-- Clean up
USE master;
GO
IF SUSER_SID('SharedLogin') IS NOT NULL DROP LOGIN SharedLogin;
IF DB_ID('ChainedDB') IS NOT NULL DROP DATABASE ChainedDB;
IF DB_ID('SourceDB') IS NOT NULL DROP DATABASE SourceDB;
View Code

總結
跨數據庫所有權鏈接是SQL Server幫助保持你的數據安全的另一種方式。禁用這個選項,惡意用戶訪問其他數據庫中的數據就會更難。但在正確的場景和安全環境中,你可以啟用此選項讓數據庫對象所有者對其數據有更嚴格的控制。你應該很少在服務器級別啟用跨數據庫所有權鏈接。相反,只為你真正需要的數據庫啟用,並確保不被特權用戶濫用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM