Godtear.Security,基于CAS(Code Access Security)的权限控制(一)
你是否曾以管理员的身份登录系统并在无意中下载或运行了木马,病毒?你的软件是否曾因为引用第三方组件而意外地访问了敏感资源?尽管系统提供了完整的角色管理和权限控制,但伪装的恶意程序还是一次次地被执行。究其原因在于传统的安全机制对权限控制的深度和粒度是不足的。多数普通安全机制根据用户的登录凭据(通常为密码)赋予用户权限,并限制允许用户访问的资源(通常为目录和文件)。但是,这种方法无法解决以下几个问题:用户从许多来源获取代码,这些来源中有一些可能不可靠;代码可能包含 bug 或具有脆弱性,使它有可能被恶意代码利用;代码有时候会执行一些操作,而用户并不知道它会执行这些操作。结果,当谨慎且可信的用户运行恶意软件或包含错误的软件时,计算机系统可能会损坏,私有数据可能会泄漏。多数操作系统安全机制要求每一段代码都必须完全受信任(Web 页的脚本可能除外),然后才可运行。因此,仍需要一种可广泛应用的安全机制,即使两个计算机系统之间没有信任关系,该机制也允许在一个计算机系统上生成的代码能够在另一系统上安全地执行。所幸.NET平台提供了除角色访问安全外的代码访问安全机制。
一,CAS(代码访问安全)介绍
NET Framework提供的CAS(Code Access Security/代码访问安全)机制则是一种更细粒度的安全机制,代码访问安全性使代码可以根据它所来自的位置以及代码标识的其他方面,获得不同等级的受信度。代码访问安全性还实施不同级别的对代码的信任,从而最大限度地减少了必须完全信任才能运行的代码的数量。使用代码访问安全性,可以减小恶意代码或包含错误的代码滥用您的代码的可能性。它可以减轻您的责任,因为您可以指定应该允许代码执行的一组操作,同时还可指定永远不允许代码执行的一组操作。代码访问安全性还有助于最大限度地减少由于代码中的安全脆弱性而造成的损害。
代码访问安全性是帮助限制代码对受保护的资源和操作的访问权限的一种机制。在 .NET Framework 中,代码访问安全性执行下列功能:
· 定义权限和权限集,它们表示访问各种系统资源的权限。
· 使管理员能够通过将权限集与代码组关联来配置安全策略。
· 使代码能够请求运行所需权限以及其他一些有用的权限,以及指定代码绝对不能拥有哪些权限。
· 根据代码请求的权限和安全策略允许的操作,向加载的每个程序集授予权限。
· 使代码能够要求其调用方拥有特定的权限。
· 使代码能够要求其调用方拥有数字签名,从而只允许特定组织或特定站点的调用方来调用受保护的代码。
· 通过将调用堆栈上每个调用方所授予的权限与调用方必须拥有的权限相比较,加强运行时对代码的限制。
为了确定是否已授予代码访问资源或执行操作的权限,运行库的安全系统遍历调用堆栈,将每个调用方所授予的权限与目前要求的权限相比较。如果调用堆栈中的任何调用方没有要求的权限,则会引发安全性异常,并会拒绝访问。堆栈步旨在防止引诱攻击;在这种攻击中,受信程度较低的代码调用高度信任的代码,并使用高度信任的代码执行未经授权的操作。在运行时要求所有调用方都拥有权限会影响性能,但这对于帮助保护代码免遭受信程度较低的代码的引诱攻击至关重要。若要优化性能,可以使代码执行较少的堆栈步;但是,任何时候这样做时均必须确保不会暴露安全缺陷。
下图阐释“程序集 A4”中的方法要求其调用方拥有权限 P 时引起的堆栈步。
二,CAS(代码访问安全)类库
.NET Framework 提供了一组代码访问权限类,它们旨在帮助保护一组特定的资源和操作。
内建的码访问权限类主要包含在三个命名空间中:
System.Security;
System.Security.Permissions;
System.Security.Principal;
1, IPermission接口
IPermission接口定义了各种权限类型应该实现的方法。
方法名 |
说明 |
IPermission Copy () |
创建并返回当前权限的副本 |
Void Demand() |
验证权限,如果不符合安全性要求则抛出SecurityException异常 |
IPermission Intersect(IPermission target) |
创建并返回当前权限和指定权限的交集权限 |
Bool IsSubsetOf(IPermission target) |
判断当前权限是否为指定权限的子集 |
IPermission Union(IPermission target) |
创建并返回当前权限和指定权限的并集权限 |
2, CodeAccessPermission类
CodeAccessPermission 抽象基类定义所有代码访问权限的基础结构。.Net Framework内建许多的权限对象都继承于此类,用于对各种资源的访问进行安全控制:
名称 |
说明 |
Data.Odbc.OdbcPermission |
使用ADO.NET ODBC Provider的权限 |
Data.OleDb.OleDbPermission |
使用ADO.NET OLE DB Provider的权限 |
Data.SqlClient.SqlClientPermission |
使用ADO.NET SQL Client Provider的权限 |
Data.OracleClient.OraclePermission |
使用ADO.NET Oracle Provider的权限 |
Drawing.Printing.PrintingPermission |
打印功能的权限控制 |
Messaging.MessageQueuePermission |
MSMQ的权限控制 |
Net.DnsPermission |
访问DNS的权限控制 |
Net.SocketPermission |
使用Socket的权限控制 |
Net.WebPermission |
访问WEB的权限控制 |
Security.Permissions.EnvironmentPermission |
改变系统环境变量的权限控制 |
Security.Permissions.FileDialogPermission |
使用文件对话框的权限控制 |
Security.Permissions.FileIOPermission |
文件操作的权限控制 |
Security.Permissions.IsolatedStoragePermission |
独立存储的权限控制 |
Security.Permissions.ReflectionPermission |
反射的权限控制 |
Security.Permissions.RegistryPermission |
注册表访问的权限控制 |
Diagnostics.EventLogPermission |
事件日志的权限控制 |
Diagnostics.PerformanceCounterPermission |
访问性能记数器的权限控制 |
DirectoryServices.DirectoryServicesPermission |
Activate Directory的访问权限 |
ServiceProcess.ServiceControllerPermission |
Service的控制权限 |
Security.Permissions.SecurityPermission |
一般性的安全权限,如Reflection、Unmanaged Code等等 |
Security.Permissions.UIPermission |
UI的权限控制 |
Web.AspNetHostingPermission |
ASPNET宿主权限 |
PrincipalPermission |
基于身份主体的权限控制,唯一基于角色的权限 |
当然我们也可以实现IPermission接口或者继承CodeAccessPermission来自定义权限对象以实现更精细和灵活的安全控制。这将在后续的文章中详细说明
三,CAS(代码访问安全)示范
1, 以编程方式请求权限
l 隐式的权限
在赋予权限时,通常有一些隐式的语句可以赋予其他权限。例如,如果赋予了访问C:"的权限FileIOPermission,就隐含着也可以访问C:"的子目录(这是Windows账户安全性允许的)。如果要检查赋予权限时是否隐式地赋予了其他的权限作为子集,可以使用下面的代码:
代码的执行结果如下:PermissionB is a subset of PermissionA
l 验证权限
下面代码演示了在访问F:"textfile.txt前验证文件的访问权限,如果不满足安全要求则打印:no rights to access this file.
l 拒绝权限
下面的代码演示了在调用不信任的代码前拒绝对文件目录的访问权限,从而防止不被信任的代码可能对文件的非法访问。不被信任的代码调用完后通过CodeAccessPermission.RevertDeny()解除前面的权限拒绝。
下面的代码演示了只授予后续不被信任的代码对目录的只读权限,以防止不被信任的代码对文件的非读访问。不被信任的代码调用完后通过CodeAccessPermission.RevertPermitOnly解除前面的只读控制。
l 断言权限
Assert(断言)可以让被调用者执行调用者没有的权限。下面的代码演示了被调用者断言自己对文件访问的权限从而避开调用者对其文件访问权限的拒绝。
随意使用断言是非常危险的,阻止断言的方法是在调用者程序集中申明禁止断言:
[assembly: SecurityPermission(SecurityAction.RequestRefuse, Assertion = true)]
2, 以声明方式请求权限
除了要求强制通过代码的权限,可以使用属性以请求权限。 属性可以使用类或单个方法时才相关联。
如果自定义一个权限则应该定义个相应的Attribute类来为你的自定义权限发出声明式要求。声明的安全属性必须直接或者间接从SecurityAttribute派生。如果该权限是代码访问权限,则属性类从CodeAccessSecurityAttribute 派生,后者从 SecurityAttribute 派生。安全属性类必须实现 CreatePermission 方法,该方法根据关联的自定义权限创建权限对象的一个实例。请注意,此关联的自定义权限类必须用SerializableAttribute 标记,才能由编译器序列化到元数据中。另外, 自定义权限的属性版本必须定义在不引用它的程序集中。自定义权限也应定义在该程序集中。对于声明式安全,这是必需的,因为该属性是在程序集加载时执行的,在遇到对该属性的引用时,可能还未创建该属性。如果试图在定义声明式权限的程序集中使用该权限,将导致引发TypeLoadException。自定义权限及其声明属性将在后续的文章中详细阐述。