译自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