文件和目录的访问控制
一、访问控制列表
权限的概念相信你已经不陌生了,那么如何设置一个文件的访问权限呢?编程可不可以实现动态的控制文件权限信息呢?答案是肯定的,.NET可以做到这些。
自由访问控制列表(Discretionary Access Control List,DACL)(有时缩写为ACL)是一种 Microsoft Windows NT 和更高版本用于保护资源(例如文件和文件夹)的机制。DACL包含多个访问控制项(Access Control Entry,ACE)。访问控制项将一个主体(通常是一个用户账户或用户账户组)与一个控制资源的使用的规则相关联。通过DACL 和ACE,可以基于与用户账户关联的权限允许或拒绝资源的权限。例如,可以创建一个ACE,并将其应用于某个文件的DACL,以阻止除管理员以外的任何人读取该文件。
系统访问控制列表(System Access Control List,SACL)(有时称为审核 ACE)是一种控制与资源关联的审核消息的机制。与DACL相似,SACL包含定义指定资源的审核规则的 ACE。通过审核ACE,可以记录访问资源的成功尝试或失败尝试,但与访问ACE不同的是,审核ACE不控制哪些账户可以使用某个资源。例如,可以创建一个ACE并将其应用于某个文件的SACL,以记录打开该文件的所有成功尝试。
System.Security.AccessControl命名空间通过一些方便的类(这些类抽象化Windows ACL 安全系统的大部分复杂性)提供对访问控制列表(ACL)的访问。此外, System.Security.AccessControl命名空间还包含几个提供对Windows ACL安全系统的高级访问的类。
.NET Framework提供对下列资源的ACL的访问:加密密钥、目录、事件等待句柄、文件、Mutexes、注册表项、信号量。
上述每个资源都有几个用于创建和修改ACL的类,本节主要关注目录和文件的访问控制。这些类如下:
DirectorySecurity类
该类表示目录的访问控制和审核安全。该类指定系统目录的访问权限以及访问尝试的审核方式。此类将访问和审核权限表示为一组规则,每个访问规则由一个FileSystemAccessRule对象表示,而每个审核规则由一个FileSystemAuditRule对象表示。
FileSecurity类
该类指定系统文件的访问权限以及如何审核访问尝试。此类将访问和审核权限表示为一组规则,每个访问规则由一个 FileSystemAccessRule 对象表示,而每个审核规则由一个 FileSystemAuditRule 对象表示。使用该类可检索、添加或更改表示文件的 DACL 和 SACL 的访问规则。
DirectorySecurity类和FileSecurity类是对基础Microsoft Windows文件安全系统的抽象。在此系统中,每个目录都有一个自由访问控制列表(DACL)和一个系统访问控制列表(SACL),前者控制对目录的访问,后者指定要审核的访问控制尝试。FileSystemAccessRule和FileSystemAuditRule 类是对组成DACL和SACL的访问控制项(ACE)的抽象。
二、添加访问控制
对文件和目录访问控制的操作基本相同,对于同一种操作本书在通常情况下不重复举例,读者可自行实践。
代码清单7-9是一个简单的示例,用来演示对文件添加访问控制。
代码清单 7-9 对文件添加访问控制
using(FileStream file = new FileStream(@"E:\AclTest\acltest.txt", FileMode.Open, FileAccess.ReadWrite)) { FileSecurity security = file.GetAccessControl(); FileSystemAccessRule rule = new FileSystemAccessRule( new NTAccount(@"XuanHunComputer\xuanhun"), FileSystemRights.Read, AccessControlType.Allow); security.AddAccessRule(rule); file.SetAccessControl(security); }
现在通过分析代码清单7-9来了解控制添加单个文件访问控制的细节。首先要做的是获取对文件的访问实例,这里使用FileStream,也许使用File或者FileInfo是你更喜欢的选择。通过对调用GetAccessControl方法来检索该文件的安全对象(类型为FileSecurity);除了包含其他内容以外,该对象还包含一组有序的访问规则,它们共同确定了各种用户和组对该文件所具有的权利。在该示例中,将一个新的访问规则添加到FileSecurity对象中,以便向名为xuanhun的用户授予文件的访问权。在更改生效之前,必须将其持久保存在存储器中。最后这个步骤是通过调用SetAccessControl方法完成的。
代码清单7-9说明了如何向现有文件分配访问权,那么如何在创建文件的初始就分配权限呢?这样做有一个重要的安全原因:可确保安全的对象总是用一些默认的安全语义创建的。默认情况下,分层式资源管理器(例如文件系统或注册表)中的对象从其父对象中继承它们的安全设置,文件从它们的父目录中继承它们的安全设置。默认权利取决于所创建对象的类型,而且可能不是您所希望的那样。例如,您很少会有意创建每个人都具有完全访问权限的对象,但这却可能恰好是默认安全设置所指定的权限。不能简单地用默认安全设置创建对象并且在以后修改这些设置,产生此问题的原因是:在已经创建对象之后对其加以保护会打开一个机会窗口(在创建和修改之间),在此期间,该对象可能被劫持。劫持可能导致创建者失去对刚刚所创建对象的控制,这会造成灾难性的后果。代码清单7-10演示了在如何创建文件时配置访问规则。
代码清单 7-10 为新创建文件添加规则
FileSecurity security = new FileSecurity(); FileSystemAccessRule rule = new FileSystemAccessRule( new NTAccount(@"XuanHunComputer\xuanhun"), FileSystemRights.Read, AccessControlType.Allow); security.AddAccessRule(rule); FileStream file = new FileStream( @"M:\temp\sample.txt", FileMode.CreateNew, FileSystemRights.Read, FileShare.None, 4096, FileOptions.None, security);
代码清单7-10与代码清单7-9执行的是相同的操作,但顺序不同,并且无需持久保存更改(因为对象是全新的)。在创建文件之前,先创建一个FileSecurity对象,并且用所需的访问规则填充它。随后,FileSecurity实例被传递给文件的构造函数,该文件从一开始就被正确地加以保护。
三、访问规则
访问规则有两种类型:“允许”(allow)和“拒绝”(deny)。可以通过检查规则的AccessControlType属性来确定相应规则的类型。按照约定,拒绝规则总是优先于允许规则。因而,如果向某个对象中添加下列两个规则:“授予每个人读、写访问权限”和“拒绝Xuanhun写访问权限”,则Xuanhun将被拒绝进行写访问。
想要枚举文件或者目录的访问规则时,可以使用如代码清单7-11所示的方式。
7-11 枚举文件访问规则
class Program { static void Main(string[] args) { string path = @"e:\AclTest\acltest.txt"; FileSecurity security = File.GetAccessControl(path); foreach (FileSystemAccessRule rule in security.GetAccessRules(true, true, typeof(NTAccount))) { Console.WriteLine("{0} {1}---- {2}", rule.AccessControlType == AccessControlType.Allow ? "授权" : "拒绝",rule.IdentityReference.ToString(), rule.FileSystemRights ); } Console.Read(); } }
以上代码使用File类的GetAccessControl方法获取FileSecurity对象,然后通过FileSecurity类的GetAccessRules方法获取当前文件的访问规则。对于目录的操作与此基本类似,相应地,使用的是DirectorySecurity对象。
GetAccessRules方法有三个参数:第一个参数表示是否要包括为对象显式设置的访问规则;第二个参数表示是否要包括继承的访问规则;第三个参数表示访问规则的类型。代码清单7-11的运行结果如图7-9所示。
7-9 枚举文件访问规则的运行结果
为方便起见,访问规则被公开为集合。该集合是只读的,因此对它的规则进行的所有修改都必须通过FileSecurity对象的专用方法(例如AddAccessRule、SetAccessRule和RemoveAccessRule)执行。集合内部的规则对象也是不可改变的。要了解为什么拒绝规则优先于允许规则,必须知道访问检查算法是如何工作的。当执行访问权限检查时,将按照规则在访问控制列表内部出现的顺序对它们进行评估。在代码清单7-11中,当检查用户Xuanhun的访问权限时,会首先评估拒绝Xuanhun读取访问的规则,然后再评估授予BUILTIN\Everyone读取和执行访问权限的规则。一旦做出允许或拒绝的决策,评估就将停止。这就是拒绝规则“生效”的原因。如果它们被放置在允许规则之后,则它们不会总是执行它们的预期功能。
和可以添加新的访问规则一样,还可以移除现有的访问规则。但是注意,在从用户那里撤回某项权限和完全拒绝该权限之间存在差异。例如,假设Xuanhun是“全职雇员”组的成员,并且被设置为:“全职雇员可以读取文件”和“Xuanhun具有读写访问权限”。根据这一方案,撤消Xuanhun的读取权利会产生下列规则:“全职雇员可以读取文件”和“Xuanhun具有写入访问权限”。这恐怕不是您所期望的结果,因为撤消Xuanhun的读取访问权限没有产生任何效果:Xuanhun仍然可以作为全职雇员获得读取访问权限。如果你的目标是确保不会将访问权限授予Xuanhun,达到该目标的唯一方式是添加一个拒绝规则。此外,如果该对象根本不包含任何访问规则,那么每个人都将被拒绝对该对象的所有访问权限。
继承
我们只考虑了不具有子对象的简单的叶子对象。一旦从叶子对象(例如文件、信号量和互斥锁)转向容器对象(例如目录、注册表项和Active Directory容器),事情就变得复杂了。额外的复杂性源自以下事实:容器的访问规则可能被配置为不仅应用于对象本身,而且还应用于它的子对象、子容器或这两者。这就涉及继承和传播设置的领域。
每个访问规则不是显式的就是继承的(用IsInherited属性来确定),显式规则是那些已经通过在对象上执行的显式操作添加到该对象的规则;相反,继承规则来自于父容器。在使用对象时,只能操纵它的显式规则。
在向容器中添加新的显式规则时,可以指定两组标志:继承标志和传播标志。继承标志有两个:容器继承(Container Inherit,CI)和对象继承(Object Inherit,OI)。指定容器继承的规则将应用于当前容器对象的子对象,对象继承规则应用于叶子子对象。当传播标志被设置为None时,这些关系是可传递的:它们将跨越当前容器下层次结构的整个子树,并且应用于该容器的子对象、孙子对象等。
规则的顺序是很重要的,因为它确定了优先顺序,并最终影响到对象的访问方式。尽管无法更改默认顺序,但明白一组规则将被授予哪个类型的访问权限是很重要的。最重要的是,所有继承规则总是跟在显式规则后面。这样,显式规则总是优先于继承规则,父规则优先于祖父规则,等等。
父对象和子对象
如果希望对象避开由其父对象给予它的安全语义,会发生什么情况呢?实际上,确实存在可以声明“我的父对象的安全设置将不再适用于我”的机制。此时,甚至可以指定是否希望在该情况下使继承规则保持原样,但是在父对象的设置发生更改时拒绝“侦听”,另外,可以彻底清除所有继承规则。这是通过访问控制保护实现的,如代码清单7-12所示:
7-12 访问控制保护
using(FileStream file = new FileStream( @"M:\temp\sample.txt", FileMode.Open, FileAccess.ReadWrite)) { FileSecurity security = file.GetAccessControl(); security.SetAccessRuleProtection( true, false ); file.SetAccessControl(security);}
以上代码中需要说明的是SetAccessRuleProtection方法,该方法设置或移除与此ObjectSecurity 对象关联的访问规则的保护。受保护的审核规则不会通过继承被父对象修改。它的第一个参数标识访问规则是否被继承,如果为true则不被继承,否则继承。第二个参数标识是否保留继承的访问规则,如果保留为true,去除则为false。
注意 尽管可以使用该技术避免从父对象那里收到继承设置,但没有办法收到父对象不打算给予你的继承设置,传播只会发生在其ACL没有受到保护的对象上。可以做的唯一事情就是在父对象改变主意之前获得继承设置的快照,因为一旦访问规则受到保护,它们就将保持这个状态,并且父对象无法重写它们。
所有者
“所有者”(owner)的概念对于对象安全性是很特殊的。所有者被赋予了特殊的权力,即使与对象相关联的规则禁止用户访问该对象,但如果该用户是所有者,则他仍然可以重写现有规则,并重新获得对该对象的控制。完成该操作的过程与访问规则的常规操作过程没有什么不同。安全对象还允许更改所有者,但是操作系统将禁止其他人执行该操作。通常,为了更改所有者,必须具有对象的TakeOwnership权限或者具有特殊的“取得所有权”(Take Ownership)特权更改所有者如下所示(假设你具有这样做的权利):
FileSecurity security = file.GetAccessControl(); security.SetOwner(new NTAccount(@"Administrators\Xuanhun")); file.SetAccessControl(security);
以上代码中使用SetOwner方法来制定当前文件的所有者为NTAccount类型用户Xuanhun。
此外,还可以查看对象的当前所有者是谁,或者请求将所有者作为安全标识符或Windows NT账户对象返回,如以下代码所示:
SecurityIdentifier sid = (SecurityIdentifier)security.GetOwner(typeof(SecurityIdentifier)); Console.WriteLine(sid.ToString()); NTAccount nta = (NTAccount)security.GetOwner(typeof(NTAccount)); Console.WriteLine(nta.ToString());
以上代码使用两种形式来查看所有者:一种是SecurityIdentifier对象,另一种是NTAccount对象。SecurityIdentifier类表示一个安全标识符 (SID) 并为 SID 提供封送处理和比较操作。NTAccount类表示一个用户或组账户。
四、审核规则
到目前为止,只是讨论了访问控制规则,它们构成了对象的DACL。DACL可以由对象的所有者任意更改,还可以由所有者已经给予其更改DACL权限的任何人更改。对象的安全描述符包含另一个规则列表,称为系统访问控制列表(System Access Control List,SACL),该列表将控制系统对对象执行哪个类型的审核。
审核是一种具有安全敏感性的操作。在Windows中,审核只能由本地安全机构(Local Security Authority,LSA)生成,因为LSA是唯一允许向安全事件日志(这里存储了审核)中写入项的组件。安全审核是一项非常严谨的业务,可以在计算机法庭中根据事实分析谁做了什么事情,以及谁试图在系统中做什么事情。很多组织都长年保留它们的审核日志。不用说,规定对哪些项目进行审核的设置通常都受到严格的管理控制。如果执行该节中的代码并且遇到UnauthorizedAccessException消息,可能是因为运行时所在的账户不包含“安全特权”(Security Privilege)。为了能够修改甚至分析SACL,必须由本地计算机策略向你的账户分配这一强大的特权。尽管有这些可怕的警告,但在具有必要的特权之后,读取和操作对象的审核设置在所有方面都类似于修改访问控制设置。代码清单7-13是一个操作审核规则的简单示例。
代码清单7-13 操作审核规则
using(FileStream file = new FileStream( @"M:\temp\sample.txt",FileMode.Open, FileAccess.ReadWrite)) { FileSecurity security = file.GetAccessControl(); FileSystemAuditRule rule = new FileSystemAuditRule( new NTAccount( @"FABRIKAM\Full_Time_Employees"), FileSystemRights.Write, AuditFlags.Failure); security.AddAuditRule(rule); file.SetAccessControl(security) }
与之前的代码示例不同的是,本示例使用一个新的FileSystemAuditRule类。该类表示基础访问控制项(ACE)的抽象,该访问控制项指定用户账户、要提供的访问的类型(读、写等),以及是否要执行审核。此类还可以指定如何从对象继承审核规则以及将审核规则传播到对象。
若要在 Microsoft Windows NT 上允许文件或目录审核,必须在自己的计算机上启用Audit Access Security策略。默认情况下,该策略设置为No Auditing。
启用 Audit AccessSecurity 策略的步骤如下:
步骤 1 打开 Local Security Settings Microsoft 管理控制台 (MMC) 管理单元,定位于 Administrative Tools 文件夹中。
步骤 2 展开 Local Policies 文件夹,左击 Audit Policy 文件夹。
步骤 3 在该 MMC 管理单元的右窗格上双击 Audit object access 项,或右击并选择属性选项以显示 Audit object access Properties dialog。
步骤 4 选中Success 或 Failure 框以记录成功或失败。
注意 用户账户的审核规则需要同一用户账户的对应访问规则。
如代码清单7-13所示,需要使用FileSystemAuditRule类创建新的审核规则,然后使用FileSecurity或DirectorySecurity类可持久保存此规则。
审核设置被表示为审核规则。可以指定你想要审核的安全主体(用户或组)的名称、感兴趣的访问权限类型(例如读取、写入等)以及你是希望在授予、拒绝访问权限还是在执行这两种操作时生成审核。例如,在代码清单7-13中,每当全职雇员被拒绝对某个文件或给定父目录下的目录进行写入访问时,系统都将生成审核。继承标志、传播标志和保护设置对审核规则的作用方式与它们对访问控制规则的作用方式完全相同。