SQL Server 2008 仔细检查数据库中主体所拥有的各种权限,仅当主体具有所有者身份或拥有对象的某些权限时,才会显示该对象的元数据。还有一种 VIEW DEFINITION 权限,即使没有该对象的其他权限,利用它也能查看元数据信息。 细粒度权限架构的一个优点就是 SQL Server不仅保护元数据,也保护数据。在 SQL Server 2005之前,能够访问数据库的用户可以看到数据库中所有对象的元数据,无论该用户是否可以访问其中的数据或是否能够执行存储过程。
SQL Server 2008 仔细检查数据库中主体所拥有的各种权限,仅当主体具有所有者身份或拥有对象的某些权限时,才会显示该对象的元数据。还有一种 VIEW DEFINITION 权限,即使没有该对象的其他权限,利用它也能查看元数据信息。
这种保护扩展到了某些操作返回的出错消息,这些操作试图访问或更新用户无权访问的对象。SQL Server 不会确认确实存在一份名为 Address 的表,使攻击者确信自己的方向是正确的,而是返回提示各种可能性的出错消息。例如,如果用户无权访问数据库中的对象,并且试图访问 Address 表,则 SQL Server 将显示下列出错消息:
Msg 3701, Level 14, State 20, Line 1
Cannot drop the table 'Address', because it does not exist or you do not have permission.
通过这种方式,攻击者无法确认是否真的存在 Address 表。但是,通过排查问题,仍然有很小的攻击可能性。
SQL Server Agent 代理
关于 SQL Server 2008 中的授权模型,一个最佳示例就是 SQL Server Agent。可以定义往往与 Windows 登录相关的各种凭证,且它们将链接到具有必要权限以执行一个或多个 SQL Server Agent 步骤的用户。然后,SQL Server Agent 代理将凭证与工作步骤链接到一起,以提供必要的权限。
这就以颗粒方式实现了“最少特权”原则:只授予工作步骤必要的权限,仅此而已。可以随意创建代理,并使其与一个或多个 SQL Server Agent 子系统相关联。这与 SQL Server 2000中的全能式代理帐户截然不同,后者允许用户在 SQL Server Agent 的任何子系统中创建工作步骤。
注释 在 SQL Server 2000中升级服务器时,将创建单代理帐户,而且所有的子系统都将被分配这个单代理帐户,以使现有的工作继续运行。升级完成后,将创建凭证和代理帐户,通过实施更安全、粒度更细的代理集,以保护服务器资源。
图6为 Management Studio 中的 Object Explorer,它显示了 SQL Server Agent 中可用的子系统列表。每个子系统都可以拥有一个或多个与之相关的代理,以为工作步骤分配适当的权限。该架构有一个例外,即 Transact-SQL 子系统需要在模块所有者的权限下执行,这与 SQL Server 2000 中的方式相同。
新安装了 SQL Server 之后,只有 System Administrator 角色有权维护 SQL Server Agent工作,而且只有 sysadmins 能够使用 Management Studio Object Explorer 中的管理窗格。SQL Server 2008 允许用户利用其它一些角色授予各种级别的权限。可以分配用户 SQLAgentUser、SQLAgentReaderRole 或 SQLAgentOperator 角色,其中每一个角色都会分配级别逐渐提高的权限,以创建、管理及运行工作,也可分配 MaintenanceUser 角色,它拥有 SQLAgentUser 的所有权限,还能创建维护计划。
当然,sysadmin 角色的成员可以在任何子系统中随意执行任何操作。要授予其他任何用户使用子系统到权限,需要创建至少一个代理帐户,这可授予访问一个或多个子系统的权利。图7显示了如何将代理帐户 MyProxy 分配多个主体,这里包括一位用户和一个角色。代理帐户使用凭证,将其链接到帐户,通常是链接到域帐户,并且拥有在操作系统中执行各种任务的权限,这些权限也是子系统所必需的。每个代理都拥有一个或多个与之相关的子系统,它们使主体能够运行这些子系统。
下列代码就是实施图7所示架构所需的 Transact-SQL 代码。首先创建凭证和一个数据库对象,后者将提供操作系统帐户链接,该帐户有权在子系统中执行所需动作。然后它添加 MyProxy 代理帐户,该帐户其实只是凭证的友好名称。接着,它将代理分配两个主体,就是 SQL Server 登录和定制角色。最后,它使代理与4个 SQL Server Agent 子系统相关联。
CREATE CREDENTIAL MyCredential WITH IDENTITY = 'MyDOMAIN\user1'
GO
msdb..sp_add_proxy @proxy_name = 'MyProxy',
@credential_name = 'MyCredential'
GO
msdb..sp_grant_login_to_proxy @login_name = 'MyLogin',
@proxy_name = 'MyProxy'
GO
msdb..sp_grant_login_to_proxy @login_name = 'MyRole',
@proxy_name = 'MyProxy'
GO
sp_grant_proxy_to_subsystem @proxy_name = 'MyProxy',
@subsystem_name = 'ActiveScripting'
GO
sp_grant_proxy_to_subsystem @proxy_name = 'MyProxy',
@subsystem_name = 'CmdExec'
GO
sp_grant_proxy_to_subsystem @proxy_name = 'MyProxy',
@subsystem_name = 'ANALYSISQUERY'
GO
sp_grant_proxy_to_subsystem @proxy_name = 'MyProxy',
@subsystem_name = 'DTS'
GO
SQL Server Management Studio 完全支持凭证和代理的创建,如图8所示。这将创建与上一段代码相同的代理。
代理不只是操作系统中杜绝安全问题的一种方式。如果与代理一起使用的凭证没有 Windows 权限,如通过网络写入目录的权限,则该代理也不会有此权限。也可利用代理为xp_cmdshell 授予有限的执行权限,因为它是黑客最喜欢利用的工具,只要他们能够成功威胁到 SQL Server 计算机的安全,就会进一步利用该工具将其威胁范围扩展到网络空间。代理提供了该领域的保护机制,因为纵使主体在网络上不受任何限制,例如域管理员主体,通过代理执行的任何命令也只拥有凭证帐户的有限权限。
执行上下文
一直以来,SQL Server 都支持所有权链的概念,它可确保管理员和应用程序开发人员能够在数据库的入口点提前检查权限,而不是用来提供所有被访问对象的权限。只要调用模块(存储过程或函数)或视图的用户拥有模块的执行权限或视图的选择权限,而且模块或视图的所有者曾是被访问对象的所有者(所有权链),则不会检查基本对象的任何权限,而且调用者将收到请求的数据。
假如因为代码的所有者没有被引用对象的所有权,而导致所有权链被打断,SQL Server 将按照调用者的安全上下文检查权限。如果调用者拥有访问对象的权限,SQL Server 将返回数据。如果没有此权限,SQL Server 将提示出错。
所有权链有一些局限性:它只适用于数据操作,而不适用于动态 SQL。而且,如要跨越所有权边界访问对象,就不可能实现所有权链。因此,权限提前检查行为只适用于某些场合。
SQL Server 2008 能够利用执行上下文标记模块,这样模块中的语句即可供与调用用户并列的特殊用户执行。通过这种方式,调用用户仍然需要模块的执行权限,而SQL Server 将按照模块的执行上下文检查模块中语句的权限。可以利用此行为客服所有权链的某些缺陷,因为它适用于模块中的所有语句。想要执行权限提前检查的管理员可以利用执行上下文达到这一目的。
在定义用户定义函数(除了内联的表值)、存储过程和触发器时,可以利用 EXECUTE AS 子句指定SQL Server 要使用哪个用户的权限验证对对象以及过程引用数据的访问:
CREATE PROCEDURE GetData(@Table varchar(40))WITH EXECUTE AS 'User1'
SQL Server 2008 提供了4种 EXECUTE AS 选项。
EXECUTE AS CALLER 指定代码在模块调用者的安全上下文中执行。不会出现假冒的现象。调用者必须拥有所有被引用对象的访问权限。但是,SQL Server 只检查被打断的所有权链的权限,假如代码的所有者也拥有基本对象的所有权,则只检查该模块的执行权限。这就是向后兼容性的默认执行上下文。
EXECUTE AS 'user_name' 指定代码在指定用户的安全上下文中执行。如果不想使用所有权链,这就是一个不错的选项。否则,可以创建拥有运行代码以及创建定制权限集所需权限的用户。
EXECUTE AS SELF 是一个快捷符号,用于为创建或更改模块的用户指定安全上下文。在内部,SQL Server 将保存与模块关联的实际用户名,而不是保存“SELF”。
EXECUTE AS OWNER 指定安全上下文就是模块执行时模块当前所有者的安全上下文。如果模块没有所有者,则将使用包含架构所有者的上下文。如想修改模块的所有者,而且不想修改模块本身,这就是一个绝佳选项。
利用 EXECUTE AS 选项更改用户上下文时,模块的创建者和更改者必须拥有指定用户的IMPERSONATE 权限。不能从数据库中删除指定用户,除非将所有模块的执行上下文更改为其他用户。
用户/架构分离
SQL Server 2000 没有架构的概念,而 ANSI SQL-99 规范将架构定义为单个主体所拥有的所有数据库对象集合,而且该主体形成了对象的一个命名空间。架构就是数据库对象的容器(如表、视图、存储过程、函数、类型和触发器等)。它的功能与.NTE Framework 和 XML 中的命名空间函数非常类似,该函数可将对象进行分组,以便数据库能够重用对象名称,如允许在一个数据库中同时存在 dbo.Customer 和 Fred.Customer,也可对不同所有者的对象进行分组。
注意:需要用到目录视图,如 sys.database_sys.principals、sys.schemas 和sys.objects 等。原因在于 sysobjects 旧系统表不支持架构,因此不支持U/S分离。此外,不赞成使用旧目录视图,在 SQL Server 的未来版本中它们将被删除。
顶端就是 SQL Server 2000中的架构工作原理。当管理员在数据库中创建 Alice 用户时, SQL Server 将自动创建 Alice 架构,它隐藏于 Alice 用户后面。如果 Alice 登录了正在运行 SQL Server 但没有数据库所有权的服务器,而且创建了表1,则该表单实际名称为 Alice.Table1。这也适用于 Alice 创建的其他对象,如 Alice.StoredProcedure1 和 Alice.View1。如果 Alice 是数据库所有者或 sysadmin,她创建的对象将成为 dbo 架构的一部分。虽然我们习惯说 dbo 拥有对象,但这是同一码事。
需要修改对象的所有权时,例如 Alice 离开公司而 Lucinda 接手了 Alice 的工作,此时SQL Server 2000 中用户和架构的融合会产生一个问题。系统管理员必须将 Alice 拥有的所有对象的所有者改为Lucinda。更成问题的是,则 Lucinda 拥有了表的所有权后,还必须将任何引用 Alice.Table1 的Transact-SQL 或客户端应用程序代码改为引用 Lucinda.Table1。根据 Alice 拥有的对象数量以及内部嵌有该名称的应用程序数量,这可能是一项繁重的工作。Microsoft 公司一直建议内置的 dbo 用户要拥有所有的数据库对象,以避免产生这些问题。与修改许多对象和客户端应用程序相比,修改数据库的所有权要容易得多。
注意:不要被 SQL Server 2000 CREATE SCHEMA 语句迷惑。它只是一种简单的方法,用以创建特殊用户拥有的表和视图,以及授予权限。可以利用该语句命名架构的所有者,但不能命名架构。SQL Server 仍不可避免地将所有者链接到架构,这要面对修改所有权带来的所有问题。
SQL Server 2008 通过分离用户和架构克服了这些问题,并实施了 SQL-99 架构,如图9底部所示。利用新增的 CREATE USER DDL 创建 Alice 新用户时,SQL Server 不再自动创建使用相同名称的架构。反之,必须显式创建架构,并将其所有权分配用户。由于图示的所有的数据库对象都包含于 Schema1 架构中,就是 Alice 最初拥有的架构,因此只须将架构的所有者改为 Lucinda,就可以方便地修改所有架构对象的所有权。每位用户也都被分配默认架构,这样 SQL Server 将假定没有架构引用且按照名称引用的任何对象都位于默认架构中。在图9的底部,如果 Alice 使用 Schema1 作为默认架构,她就可将该表命名为 Schema1.Table1 或Table1。Carol 用户可能没有与其名字关联的默认架构,必须将该表命名为 Schema1.Table1。没有默认预定义架构的任何用户都使用 dbo 作为默认架构。
在 SQL Server 2008 中,完全合格的对象名称由4部分组成,这与旧版SQL Server 中的对象名称类似:
server.database.schema.object
与旧版类似,如果对象所在服务器与运行代码的服务器同名,则可忽略服务器名称。如果连接打开了同名数据库,则可忽略数据库名称。如果使用当前用户的默认架构或架构为 dbo 所拥有,则可忽略架构名称,因为这是 SQL Server 尝试消除对象名称歧义时最后用过的架构。
可以利用 CREATE USER 语句而非 sp_adduser 语句创建新用户。此系统存储过程仍然是为了实现向后兼容性,但已进行了少许修改,以遵循用户与架构分离的新原则。sp_adduser 创建的架构与新用户名或应用程序角色同名,并将该架构作为用户的默认架构,这与 SQL Server 2000 的行为类似,但提供了分离的架构。
注意:使用 ALTER AUTHORIZATION 语句时,可能会产生这种情况:“您”拥有“我的”架构中的表(或反之)。这个问题具有重大的隐含意义。例如,谁拥有该表的触发器,您还是我?底部的代码行可以设计得非常巧妙,以发现架构范围对象或类型的真正所有者。有两种方式可以避开此问题:
利用 OBJECTPROPERTY(id,”OwnerId”)发现对象的真正所有者。
利用 TYPEPROPERTY(type,”OwnerId”)发现类型的真正所有者。
SQL Server 2008 利用同义词帮助减少击键次数。可以利用两部分、三部分或四部分完整对象名为任何对象创建同义词。SQL Server 使用同义词访问已定义的对象。在下列代码中,“History”同义词表示在AdventureWorks 数据库中指定的 schema.table。SELECT 语句返回EmployeeDepartmentHistory 表的内容。
USE AdventureWorks
GO
CREATE SYNONYM History FOR HumanResources.EmployeeDepartmentHistory
SELECT * FROM History
注意:如果其他人准备使用同义词,则管理员或所有者必须为其授予权限。针对视图、表或表值函数,可对同义词应用GRANT SELECT。针对过程或标量函数,可对同义词应用 GRANT EXECUTE,诸如此类。
也可通过以下代码,为完整的四部分名称定义“History”同义词:
CREATE SYNONYM HistoryFOR MyServer.AdventureWorks.HumanResources.EmployeeDepartmentHistory
假设当前的用户拥有使用同义词的权限以及读取表的权限,则使用类似的四部分全名即可在其他数据库上下文中使用同义词:
USE pubsSELECT * FROM AdventureWorks..History
还要注意,如果不提供架构名称作为新同义词名称的一部分,它将成为默认架构的一部分。