当你使用CREATE ASSEMBLY语句把一个程序集加载到一个数据库中时,SQL Server提供了三种权限集级别:SAFE,EXTERNAL_ACCESS和UNSAFE。这些权限集形成如图3和图5(均请参考第二篇)所示的AppDomain策略级别。
下面是一个典型的语句,它实现安装位于FileLoader.dll文件内的一个程序集,并且赋予它EXTERNAL_ACCESS权限集。
CREATE ASSEMBLY FileAclearcase/" target="_blank" >ccess
FROM 'E:\FileLoader.dll'
WITH PERMISSION_SET = EXTERNAL_ACCESS
GO
在代码执行时,每一种权限集级别都授予该代码一组不同的CAS许可权集。下面让我们开始讨论在每一级上授予的特定许可权。
(1) SAFE
SAFE是默认的权限集。它仅授予足够的许可权来执行代码,实现不要求存取外部资源的内部计算以及存取在宿主SQL Server实例中的数据和对象。注意,SAFE代码不能存取外部的资源,因此它不能读取或写磁盘文件,不能存取任何其它SQL Server实例,或读取或写注册表。而且,该代码也必须被检验为类型安全的,这将有助于避免各种包括缓冲区溢出在内的攻击。
SAFE代码是更可靠和安全的SQLCLR代码。它能够实现用T-SQL书写的代码在数据库和服务器实例内所能实现的几乎一样的功能。它能够授予如表格1所列举的CAS许可权。从表格1中可见,该代码能够运行和读取宿主SQL Server实例中的对象和数据-借助于一种特定形式的ADO.NET连接串,或者是"context connection=true"或者是"context connection=yes"来实现。任何其它连接串都可能会导致某种安全异常。
表格1:授予给SAFE程序集的权限集。
权限 类型 限制
SecurityPermission 受限制 执行
SqlClientPermission 受限制 不能是空口令,只能使用上下文连接串
授予给一个程序集的结果权限集是列举于表格1中的许可权权限集与来自企业、机器和用户权限集的交集。因为这些级别默认会拥有所有的许可权,所以程序集仅接受列举于表格1中的权限。注意,请确保你一定要理解这些权限。
(2) EXTERNAL_ACCESS
与SAFE相比,EXTERNAL_ACCESS权限集允许有限制地存取存在于SQL Server实例外部的资源-包括磁盘文件,在其它SQL Server实例中的数据和对象,环境变量和注册表的一些部分。存取这些其它资源通常是在SQL Server服务帐户的安全上下文中进行的,但是,该代码能够模拟其它用户进行存取。这个级别授予列举于表格2中的许可权。
表格2:授予给EXTERNAL_ACCESS程序集的权限集。
权限 类型 限制
EnviromentPermission 不受限制 -
FileIOPermission 不受限制 -
RegistryPermission 受限制 仅能以读方式存取HKEY_CLASSES_ROOT,HKEY_LOCAL_MACHINE,HKEY_CURRENT_USER,HKEY_CURRENT_CONFIG和HKEY_USER
SecurityPermission 受限制 Assertion,Execution,SerializationFormatter,ControlPrincipal
KeyContainerPermission 不受限制 -
SqlClientPermission 不受限制 -
EventLogPermission 受限制 仅限于本地主机且仅限于系统管理员
DnsPermission 不受限制 -
SocketPermission 受限制 仅限于IP地址
WebPermission 受限制 仅能通过HTTP存取本地主机
SmtpPermission 受限制 仅能进行连接存取
DistributedTransactionPermission 不受限制 -
NetworkInformationPermission 受限制 仅能通过Ping方式存取
StorePermission 不受限制 -
上面不受限制的FileIOPermission可能看起来有点令人担心,因为,它意味着,从CLR的角度来看,代码能存取磁盘上的任何位置。但是切记,该代码仍然运行于本地服务帐户的操作系统安全限制下。因此如果该帐户不能存取一个文件的话,那么SQLCLR代码也不能存取。
典型地,本地服务帐户是一种具有极强权限的帐户,因此存在滥用的可能性。为此,我们一般把对这些程序集的存取权限仅授予那些具有服务帐户信任度的登录并且不使用本地系统帐户作为SQL Server的服务帐户。
值得注意的是,借助于EXTERNAL_ACCESS权限集,你可以使用一个更传统型的ADO.NET连接串来连接到在同一个SQL Server实例(SQLCLR代码在其中运行)中的一个数据库。这需要SqlClientPermission以便你能够使用一个除了"上下文连接"串以外的连接-用以读取当前实例中的数据,指定通常的服务器命名,凭证,等等。然而,我也无法找到为什么你要这样做的理由,但是既然我们可以进行选择,也是一件好事,对吗?
(3) UNSAFE
这个UNSAFE权限集是赋予所有权限的SQLCLR等价物,在这种情况下,CLR挂起所有的许可权检查。它接受单个的不受限制的SecurityPermission权限,这是CLR的授予所有权限的方式。
潜在地,一个UNSAFE程序集能够完成各种"危险性"的动作,因为它属于内在地被信任的代码。例如,它能调用非托管代码,例如COM组件和原始Win32 API。它还受限于服务帐户的操作系统许可权,但是CLR不会限制它存取任何资源的能力。
因为UNSAFE是如此的不安全,所以,只有一个sysadmin能够创建这种类型的程序集。
二、 访问外部资源
因为访问外部资源需要与操作系统进行交互,所以,当代码尝试存取外部的资源时,存在多种要遵守的规则。
对于SAFE代码来说,这种规则是简单的:如果它试图存取一个外部的资源,那么存取将被否认并且它引发一个异常。就是这些。
对于EXTERNAL_ACCESS和UNSAFE的情况,则复杂些:
· 规则1:如果代码在一个SQL Server登录的安全上下文下执行(也就是说,还没有被映射到一个Windows用户或组),那么存取将被禁止并且引发一个异常。一个SQL登录并不拥有SQL Server外的任何许可权,因此这是很重要的。
· 规则2:如果代码在一个被映射到一个Windows登录的登录下执行,那么进行外部存取的执行上下文就是该登录的执行上下文。如果该用户能够存取Windows中的资源,那么该代码成功。如果不是,存取被否认并且引发一个异常。
· 规则3:如果调用者不是原始的调用者(已经进行了一个执行上下文切换),那么存取被否认并且引发一个异常。
一开始,这些规则也使我有点糊涂,但是,当我以后逐渐地越来越多地使用它们时,它们开始变得很重要了。对于规则1来说,因为一个SQL登录仅仅存在于SQL Server世界中,所以,如果它能存取操作系统资源的话,这将是一个巨大的安全漏洞。规则2也很重要,并且它允许模拟。规则3似乎有点严格,但是我怀疑SQL Server小组有点保守,因为上下文切换可能会是一场"管理恶梦",并且他们仍然确信不会产生安全漏洞问题。
外部的存取规则甚至更复杂些。假定运行代码的登录已经成功"越过"上面列举的"重重封锁",但是,为了存取外部的资源-正如你可能盼望的(并且也许希望),SQL Server并不自动地模拟当前执行上下文。代之的是,它使用SQL Server实例的服务帐户来存取资源。或者,你也可以显式地模拟上下文登录来存取资源。这样做需要使用SqlContext对象的WindowsIdentity属性来调用WindowsIdentity来实现实际的模拟。
列表1:在SQLCLR代码中使用模拟
try
{
//模拟当前SQL安全上下文
WindowsIdentity CallerIdentity = SqlContext.WindowsIdentity;
if (CallerIdentity != null)
{
OriginalContext = CallerIdentity.Impersonate();
//做一些保护操作
}
}
catch
{
//从存取问题中恢复
}
finally
{
if (OriginalContext != null)
OriginalContext.Undo();
}
列表1向你展示如何在SQLCLR代码中使用模拟。WindowsImpersonationContext对象属于System.Security.Principal命名空间的一部分并且描述了在你模拟前的Windows用户安全上下文。SqlContext对象属于和SQL Server一起安装的Microsoft.SQL Server.Server命名空间的一部分并且提供在SQL Server主机和SQLCLR代码之间的一个钩子。在这种情况中,它使用WindowsIdentity属性来得到当前安全身份的一个令牌。这是Windows登录的安全上下文-该代码在这一登录下运行。该代码测试是否结果CallerIdentity为null,如果该代码在一种SQL登录下运行时它将为null。最后,它调用WindowsIdentity模拟来实现实际的模拟,并保存原始的上下文以用于当要求恢复到该上下文时。
必须理解,仅当执行需要保护的操作(例如打开一文件)时模拟才起作用。一旦它被打开,该代码就不再需要模拟。因此,一旦你完成保护的操作,即恢复回去。
最后,通过调用OriginalContext的Undo方法,代码块负责恢复到原始上下文。如果你在函数结束之前不进行恢复,那么SQL Server将引发一个异常。
对于模拟也存在一些限制。当该模拟起作用时,你就无法再使用SQL Server实例存取数据或对象。在你再次存取本地数据之前,你必须恢复模拟。这也意味着,进程内数据存取总是发生在会话的当前安全上下文中。
有趣的是,一个异步执行的UNSAFE程序集(也就是说,它能够创建线程并且异步地运行代码)永远不会允许进程内数据存取。其实,这并不是一个安全问题而显然是一个可靠性的问题。
三、 可信赖的数据库
在SAFE程序集和其它权限设置级别之间的另外一个区别是在SQL Server 2005测试期添加的。现在,你必须满足两个要求之一来创建EXTERNAL_ACCESS或UNSAFE程序集:
· 数据库所有者(dbo)必须拥有EXTERNAL ACCESS ASSEMBLY权限并且数据库必须拥有TRUSTWORTHY属性集。
或者:
· 程序集的使用方式必须是,通过一个具有EXTERNAL ACCESS ASSEMBLY权限的登录而且要使用一个证书或一个非对称密钥。
EXTERNAL ACCESS ASSEMBLY权限是另一种新的粒度许可权-允许一名负责人创建这类程序集。默认情况下,系统管理员拥有它并且能够把它赋给其它登录。但是这样做时要慎重,这当然因为可能潜在地允许危险代码安装到服务器中。
一个数据库的TRUSTWORTHY属性要求设置管理员权限并且是在数据库中安装非SAFE程序集的前提。基于EXTERNAL ACCESS ASSEMBLY权限,一个DBA能够控制是否存在潜在危险性的程序集能够被安装到任何数据库中,以及谁能够把它们放到那里。这很有希望会使DBA安心而不必再担心他们的服务器会感染.NET代码!
四、 总结
表格3包含可用于SQLCLR程序集的三种权限集的总结,以及SQL Server为每种权限集提供的保护类型。
· 代码存取安全是在代码内CLR托管的许可权集。
· 编程模型限制是指宿主保护属性,以及是否代码能够使用静态技术。
· 要求确认指指,当你使用CREATE ASSEMBLY语句安装它时是否SQL Server验证代码存在相对的安全性。
· 调用本机代码指示,是否代码能够调用Win32 API或对外部组件作一种平台调用。
表格3:权限设置总结。
权限集
保护类型 SAFE EXTERNAL_ACCESS UNSAFE
代码存取安全 仅执行 执行和受限存取外部资源 不受限制
编程模型限制(主机保护属性) 是 是 无
要求确认 是 是 否
调用本机代码 否 否 是
如你所见,只要你把你的代码限制为SAFE或EXTERNAL_ACCESS,SQL Server就能够提供一种对保护数据安全和服务器稳定性的SQLCLR代码的良好包装。
下面是一个考察你对于SQLCLR安全的理解的测试:
· 可以使用一个常规的ADO.NET连接串来存取另一种数据库(或者是一个Oracle或者是一个Access数据库)吗?
· 假定你现在已经了解访问外部资源,存取一个Oracle数据库的程序集需要具有什么样的SQLCLR权限集级别?
在继续阅读之前,请认真地考虑一下这两个问题吧。
提示 .NET框架中所包含的唯一的托管数据库提供者是System.Data.SqlClient。
注意,这个程序集必须被安装为UNSAFE。为什么呢?因为该代码必须使用System.Data.OleDb对象。因为这些是基于COM的对象(这意味着是非托管代码),所以程序集需要被安装为UNSAFE-因为这是能够存取非托管代码的唯一的级别。
你可能认为这是微软的与Oracle交互的方式。其实,答案是,对于访问一个微软Access数据库也是一样的,因为它也是基于OLE DB和相应的非托管代码。
我将在此斗胆说一句:UNSAFE代码永远不应该用在一个生产服务器中。除非它是能够被完全观察的代码和经过严格校验以验证它不会危害服务器;否则,你无法用一切办法来足已保证它是安全的。尽管我不排除存在关于UNSAFE代码的合法使用,但是我还是要深思熟虑到底是什么样的代码值得这样一冒险。我通常感到,对于使用扩展的存储过程也存在相同的问题,这一样存在冒险性且必须以复杂的C++代码来编写。
至此,我可以说,任何允许UNSAFE代码被安装到一个生产服务器的DBA一般都应该是一个DBA高手而不是一个普通的DBA。而且,我认为,作为一名经常与DBA交流的开发人员,他们中的大多数都是高手!
但是,SAFE代码不会比一个T-SQL存储过程带来更大的危险性,而且,当你需要存取外部资源时,EXTERNAL_ACCESS是一种合理的妥协。因此,你可以不必考虑对于未知内容的畏惧,而只需让SAFE代码加入到你的数据库中好了。然后,当它对于数据库、应用程序及用户来说是重要的情况下,再考虑EXTERNAL_ACCESS代码问题。总之,在使得这一类代码安全和可靠方面,微软的确做了一件好事情。