Windows NT安全性API简介(2)
Windows NT使用两种导致访问尝试失败返回错误5的机制:确认权限和确认特权。权限属于对象上的行为,比如挂起线程权限或读文件权限。权限总是与特定对象和已知用户相关联。例如,读文件权限必须与文件(权限应用在此文件上)和有或没有权限的用户相关联。同样,挂起线程权限除非与特定的线程和用户关联否则没有用。
特权是预先定义好的属于系统上操作的权限。例如,特权有调试程序、备份和恢复存储设备以及装入驱动程序。特权以用户为中心,而不是对象。
为了使两者之间区分得更清楚,可以看一下实现权限和特权的数据结构:权限在叫作访问控制表(ACL)的数据结构中指定。ACL通常与对象相关。用户用访问令牌表示。当用户试图访问受保护的对象时,其访问令牌与对象的ACL检查。访问令牌包含代表用户的唯一标识符(安全性ID,或SID)。ACL中的每个权限与一个SID相关;这样,安全性子系统就知道了与每个用户相关的权限。
另一方面,特权在访问令牌中编码,所以没有相关联的对象。要确定用户是否允许做某个与特权有关的操作,安全性子系统检查访问令牌。
此外,权限需要行为的说明(干什么的权限?例如,读文件或者挂起线程),而特权不需要(用户或者有特权,或者没有)。与特权相随的操作隐含在特权本身中。
特权在访问令牌中编码的原因是大多数特权不考虑安全性需求。例如,允许备份存储设备的用户必须能够绕过文件安全性。为了允许用户访问而给硬盘上每个单独的文件都加入一个新的ACE是不可行的。这样,备份存储设备的代码首先检查试图备份的用户是否拥有备份特权;如果有,单个文件的安全性就被忽略。
能够与访问令牌相关的特权集被牢固加密,不能被应用程序展开。服务器程序能够使用特殊的权限和普通的映射实现自定的安全性规则。
有两种类型的ACL:自由决定的(DACL)和系统的(SACL)。DACL管制对象访问,SACL管制审核。
控制访问
在大多数情况下,错误5是由Windows NT特有的叫作AccessCheck的Win32函数内部产生的。此函数的输入有用户的访问令牌、需要的特权和ACL。ACL主要是小数据结构(叫作访问控制元素,或ACE)的列表,每个数据结构定义一个用户或一组用户、一个权限集合以及允许或拒绝的信息。例如,ACL中可能有一个ACE写着“从纸盒中拿走鸡蛋的权限明确地拒绝给与用户Elephant和Bozo”,后面一个ACE写着“从纸盒中拿走鸡蛋的权限明确地准予给与用户Betty Crocker以及CHEFS组中所有用户”。
ACL与对象相关,可以在服务器程序中动态创建。例如,如果一个文件对象与一个ACL相关,不管何时有应用程序试图打开该文件对象,ACL就会被查询以决定是否允许运行应用程序的用户打开文件。
AccessCheck函数被许多系统函数内部调用,例如,CreateFile(用户试图在NTFS分区或命名管道上打开文件时)和OpenFileMapping。然而,Win32服务器程序能够直接调用AccessCheck,保护想保护的任何对象。
注意安全性API函数只被服务器程序调用;客户不需要或直接使用安全性。客户曾经看到的Windows NT安全性就是错误5。这使得Windows NT安全性可以不必考虑客户运行的软件。需要的是服务器在域的安全性数据库中确认客户以及将从客户收到的请求翻译成服务器端函数调用的能力。此函数或者隐含调用AccessCheck,或者根据服务器端AccessCheck的输出发送或不发送其结果。
Windows NT security中容易混淆的部分是对AccessCheck的调用可能是非常模糊的。例如,Windows NT监控设备驱动程序安装的功能是一个非常模糊的概念。当试图添加设备驱动程序时用户要访问哪个“对象”?系统在哪里调用AccessCheck以及必要时在哪里将错误信息显示给用户?
在设备驱动程序的例子中,答案还不是太困难:因为设备驱动程序和系统通过注册表(Windows NT通过浏览注册表子树,解释每个条目,尝试执行在单独注册表项中指定的驱动程序二进制文件而装入设备驱动程序)交互,Windows NT保护的对象是注册表项,它在Windows NT中是可以得到的对象。在Win32 API层,任何操作注册表的尝试将会翻译成注册表工作的函数,例如RegOpenKey内部调用AccessCheck。
除了注册表保护外,驱动程序二进制文件也有安全性问题。一个因访问注册表被拒绝而落空的黑客仍然能够用添加了额外功能的驱动程序副本取代原有的驱动程序执行文件。这一过程不需要访问注册表,所以Windows NT如何防止这类问题呢?相当简单,通过要求驱动程序二进制文件存放在NTFS分区并限制对其访问。这样,取代驱动程序二进制文件的企图(在Win32 API层上调用DeleteFile或CreateFile时不可避免地被终止)会被AccessCheck抓住,恶意的黑客就不走运了。
系统提供的其它安全性对象可能难于说明。例如,怎样阻止用户访问被保护的网络共享?怎样阻止打开远程计算机上的服务控制管理器?系统层如何使Windows NT无懈可击?如何使一些安全性函数自己失败返回错误5,访问被拒绝?设想如果应用程序能够自由操作自己的访问令牌或调用安全性函数改变对象的特权将会出现什么情况?这种情况下,仅仅修改ACL和令牌中的项目就能够简单的绕过安全性。。这样,必须有某种“元安全性”,一种保护安全特性自己不被错误利用的机制。如何实现?
基于AccessCheck的安全性实现的一个结果是安全性严重依赖于只允许以众所周知的入口点访问安全性对象的体系结构。例如,Windows 3.1家族操作系统中的文件系统包括许多不同入口点:中断21h(与文件系统交互),中断13h(与磁盘设备驱动交互),以及几种类型的提供对文件系统访问的C运行库和Windows API函数(如OpenFile和_fopen)。从安全性观点来看,在OpenFile内部实现中调用AccessCheck这样的函数毫无用处,应用程序可以简单的调用_fopen绕过文件安全性。只有打开文件操作的所有不同调用都翻译成一个“安全性”调用才行;如果有一个执行安全性检查而另一个不执行,就会有安全性问题。
16位Windows系统中这种“开放文件系统”结构对提供如加密软硬件的公司来说是主要的麻烦。
在编写安全性服务器程序时,将程序设计的无懈可击是绝对必要的;也就是说,必须防止客户可以访问关键数据的所有方法。安全性系统的挑战之一是使关键数据无懈可击。这可能是一件相当复杂的工作,就象前面的例子中,单独保护注册表入口对于保护整个计算机的设备驱动程序是不够的。
访问权限类型
使用安全性API,系统能够帮助管制对几乎任何种类对象的访问。但“访问”的含义是什么?是不是谈论数据库字段时所使用的访问类型,还是与访问其它窗口的消息循环完全不同的某种类型?
这就是为什么“访问”在安全性API中是一个相当普遍的术语。不是像“打开、关闭、读取和写入对象的权限”这种牢固加密的访问类型,Windows NT中的访问被定义为掩码中位的集合。安全性子系统将用户访问掩码中的位与对象访问掩码中的位进行匹配。例如,这使得我们能够设计一个员工数据库,管理员可以读写工资和奖金的信息,经理可以读但不能写,其他人不能读写访问。
以相同的方式,应用程序能够定义自己的访问类型。例如,如果程序想保护一个可以共享(从几个用户都能够调用函数操作屏幕上对象的意义上)的OpenGL对象,可以为OpenGL对象能够完成的所有操作(如旋转、拉伸、反弹和移动)定义唯一的访问权限,并且为每个需要对图象进行操作的用户指定这些权限的唯一子集。
安全性API能够以三组权限工作:
- 标准权限(为每种对象类型提供相同操作的权限)。
- 特殊权限(对每个对象类型有特殊意义的权限。两种不同类型的对象可能有相同的权限掩码位,但有对权限意义的不同翻译)。
- 普通权限,概略的占位符(象GENERIC_READ和GENERIC_WRITE此类的权限,几乎适用于所有对象类型,但对不同对象类型有不同的意义)。普通权限被映射为标准权限和特殊权限。这一机制允许服务器不用实际定义操作就可以建立“读”和“写”的概念。服务器程序能够用普通权限工作,不管读写是对文件还是对数据库对象,对象自己能够确定普通权限如何翻译成特殊权限。