随着网络的越来越普及,安全问题变的越来越重要。最近在项目中遇到关于NTFS下安全权限的设置问题,希望通过编程的方式来获取,编辑,设置Windows下文件夹或文件的安全权限。以前关于Windows安全性的编程几乎从没接触过,所以只好到网上搜集相关的文章学习一番,下面是我的学习过程。
关于Windows的安全有一本很好的书,一美国人叫布朗的《Windows安全性编程》,可我是个懒人,又是穷人,所以放弃买书的想法,压根没想过,哈:)
Windows安全权限的设置是随着NTFS文件系统和Windows 2000系统发布才开始有的,在eNet上看到一篇很不错的,比较基础的谈Windows安全权限设置的文章,先把它引用过来吧。
随着动网论坛的广泛应用和动网上传漏洞的被发现以及SQL注入式攻击越来越多的被使用,WEBSHELL让防火墙形同虚设,一台即使打了所有微软补丁、只让80端口对外开放的WEB服务器也逃不过被黑的命运。难道我们真的无能为力了吗?其实,只要你弄明白了NTFS系统下的权限设置问题,我们可以对crackers们说:NO!
要打造一台安全的WEB服务器,那么这台服务器就一定要使用NTFS和Windows NT/2000/2003。众所周知,Windows是一个支持多用户、多任务(进程)的操作系统,这是权限设置的基础,一切权限设置都是基于用户和进程而言的,不同的用户在访问这台计算机时, 将会有不同的权限。DOS是个单任务、单用户的操作系统。但是我们能说DOS没有权限吗?不能!当我们打开一台装有DOS操作系统的计算机的时候,我们就 拥有了这个操作系统的管理员权限,而且,这个权限无处不在。所以,我们只能说DOS不支持权限的设置,不能说它没有权限。随着人们安全意识的提高,权限设置随着NTFS的发布诞生了。
Windows NT里,用户被分成许多组,组和组之间都有不同的权限,当然,一个组的用户和用户之间也可以有不同的权限。下面我们来谈谈NT中常见的用户组。
Administrators,管理员组,默认情况下,Administrators中的用户对计算机/域有不受限制的完全访问权。分配给该组的默认权限允许对整个系统进行完全控制。所以,只有受信任的人员才可成为该组的成员。
Power Users,高级用户组,Power Users 可以执行除了为 Administrators 组保留的任务外的其他任何操作系统任务。分配给 Power Users 组的默认权限允许 Power Users 组的成员修改整个计算机的设置。但Power Users 不具有将自己添加到 Administrators 组的权限。在权限设置中,这个组的权限是仅次于Administrators的。
Users:普通用户组,这个组的用户无法进行有意或无意的改动。因此,用户可以运行经过验证的应用程序,但不可以运行大多数旧版应用程序。 Users 组是最安全的组,因为分配给该组的默认权限不允许成员修改操作系统的设置或用户资料。Users 组提供了一个最安全的程序运行环境。在经过 NTFS 格式化的卷上,默认安全设置旨在禁止该组的成员危及操作系统和已安装程序的完整性。用户不能修改系统注册表设置、操作系统文件或程序文件。Users 可以关闭工作站,但不能关闭服务器。Users 可以创建本地组,但只能修改自己创建的本地组。
Guests:来宾组,按默认值,来宾跟普通Users的成员有同等访问权,但来宾帐户的限制更多。
Everyone:顾名思义,所有的用户,这个计算机上的所有用户都属于这个组。
其实还有一个组也很常见,它拥有和Administrators一样、甚至比其还高的权限,但是这个组不允许任何用户的加入,在查看用户组的 时候,它也不会被显示出来,它就是SYSTEM组。系统和系统级的服务正常运行所需要的权限都是靠它赋予的。由于该组只有这一个用户SYSTEM,也许把 该组归为用户的行列更为贴切。
权限是有高低之分的,有高权限的用户可以对低权限的用户进行操作,但除了Administrators之外,其他组的用户不能访问 NTFS 卷上的其他用户资料,除非他们获得了这些用户的授权。而低权限的用户无法对高权限的用户进行任何操作。
我们平常使用计算机的 过程当中不会感觉到有权限在阻挠你去做某件事情,这是因为我们在使用计算机的时候都用的是Administrators中的用户登陆的。这样有利也有弊, 利当然是你能去做你想做的任何一件事情而不会遇到权限的限制。弊就是以 Administrators 组成员的身份运行计算机将使系统容易受到特洛伊木马、病毒及其他安全风险的威胁。访问 Internet 站点或打开电子邮件附件的简单行动都可能破坏系统。不熟悉的 Internet 站点或电子邮件附件可能有特洛伊木马代码,这些代码可以下载到系统并被执行。如果以本地计算机的管理员身份登录,特洛伊木马可能使用管理访问权重新格式化 您的硬盘,造成不可估量的损失,所以在没有必要的情况下,最好不用Administrators中的用户登陆。
Administrators中有一个在系统安装时就创建的默认用户----Administrator,Administrator 帐户具有对服务器的 完全控制权限,并可以根据需要向用户指派用户权利和访问控制权限。因此强烈建议将此帐户设置为使用强密码。永远也不可以从 Administrators 组删除 Administrator 帐户,但可以重命名或禁用该帐户。由于大家都知道“管理员”存在于许多版本的 Windows 上,所以重命名或禁用此帐户将使恶意用户尝试并访问该帐户变得更为困难。对于一个好的服务器管理员来说,他们通常都会重命名或禁用此帐户。Guests用 户组下,也有一个默认用户----Guest,但是在默认情况下,它是被禁用的。如果没有特别必要,无须启用此账户。我们可以通过“控制面板”--“管理 工具”--“计算机管理”--“用户和用户组”来查看用户组及该组下的用户。
我们用鼠标右 键单击一个NTFS卷或NTFS卷下的一个目录,选择“属性”--“安全”就可以对一个卷,或者一个卷下面的目录进行权限设置,此时我们会看到以下七种权 限:完全控制、修改、读取和运行、列出文件夹目录、读取、写入、和特别的权限。“完全控制”就是对此卷或目录拥有不受限制的完全访问。地位就像 Administrators在所有组中的地位一样。选中了“完全控制”,下面的五项属性将被自动被选中。“修改”则像Power users,选中了“修改”,下面的四项属性将被自动被选中。下面的任何一项没有被选中时,“修改”条件将不再成立。“读取和运行”就是允许读取和运行在 这个卷或目录下的任何文件,“列出文件夹目录”和“读取”是“读取和运行”的必要条件。“列出文件夹目录”是指只能浏览该卷或目录下的子目录,不能读取, 也不能运行。“读取”是能够读取该卷或目录下的数据。“写入”就是能往该卷或目录下写入数据。而“特别”则是对以上的六种权限进行了细分。读者可以自行对 “特别”进行更深的研究,鄙人在此就不过多赘述了。
下面我们对一台刚刚安装好操作系统和服务软件的 WEB服务器系统和其权限进行全面的刨析。服务器采用Windows 2000 Server版,安装好了SP4及各种补丁。WEB服务软件则是用了Windows 2000自带的IIS 5.0,删除了一切不必要的映射。整个硬盘分为四个NTFS卷,C盘为系统卷,只安装了系统和驱动程序;D盘为软件卷,该服务器上所有安装的软件都在D盘 中;E盘是WEB程序卷,网站程序都在该卷下的WWW目录中;F盘是网站数据卷,网站系统调用的所有数据都存放在该卷的WWWDATABASE目录下。这 样的分类还算是比较符合一台安全服务器的标准了。
希望各个新手管理员能合理给你的服务器数据进行分类,这样不光是查找起来方便,更重要的是这样大大的增强了服务器的安全性,因为我们可以根据需要给每个卷或者每个目录都设置不同的权限,一旦发生了网络安 全事故,也可以把损失降到最低。当然,也可以把网站的数据分布在不同的服务器上,使之成为一个服务器群,每个服务器都拥有不同的用户名和密码并提供不同的 服务,这样做的安全性更高。不过愿意这样做的人都有一个特点----有钱:)。好了,言归正传,该服务器的数据库为MS-SQL,MS-SQL的服务软件 SQL2000安装在d:\ms-sqlserver2K目录下,给SA账户设置好了足够强度的密码,安装好了SP3补丁。
为了方便网页制作员对网页进行管理,该网站还开通了FTP服务,FTP服务软件使用的是SERV-U 5.1.0.0,安装在d:\ftpservice\serv-u目录下。杀毒软件和防火墙用的分别是Norton Antivirus和BlackICE,路径分别为d:\nortonAV和d:\firewall\blackice,病毒库已经升级到最新,防火墙规 则库定义只有80端口和21端口对外开放。网站的内容是采用动网7.0的论坛,网站程序在e:\www\bbs下。细心的读者可能已经注意到了,安装这些 服务软件的路径我都没有采用默认的路径或者是仅仅更改盘符的默认路径,这也是安全上的需要,因为一个黑客如果通过某些途径进入了你的服务器,但并没有获得 管理员权限,他首先做的事情将是查看你开放了哪些服务以及安装了哪些软件,因为他需要通过这些来提升他的权限。
一个难以猜解的路径加上好的权限设置将把他阻挡在外。相信经过这样配置的WEB服务器已 经足够抵挡大部分学艺不精的黑客了。读者可能又会问了:“这根本没用到权限设置嘛!我把其他都安全工作都做好了,权限设置还有必要吗?”当然有!智者千虑 还必有一失呢,就算你现在已经把系统安全做的完美无缺,你也要知道新的安全漏洞总是在被不断的发现。权限将是你的最后一道防线!那我们现在就来对这台没有 经过任何权限设置,全部采用Windows默认权限的服务器进行一次模拟攻击,看看其是否真的固若金汤。
假设服务器外网域名为http://www.webserver.com,用扫描软件对 其进行扫描后发现开放WWW和FTP服务,并发现其服务软件使用的是IIS 5.0和Serv-u 5.1,用一些针对他们的溢出工具后发现无效,遂放弃直接远程溢出的想法。打开网站页面,发现使用的是动网的论坛系统,于是在其域名后面加个 /upfile.asp,发现有文件上传漏洞,便抓包,把修改过的ASP木马用NC提交,提示上传成功,成功得到WEBSHELL,打开刚刚上传的ASP 木马,发现有MS-SQL、Norton Antivirus和BlackICE在运行,判断是防火墙上做了限制,把SQL服务端口屏蔽了。通过ASP木马查看到了Norton Antivirus和BlackICE的PID,又通过ASP木马上传了一个能杀掉进程的文件,运行后杀掉了Norton Antivirus和BlackICE。再扫描,发现1433端口开放了,到此,便有很多种途径获得管理员权限了,可以查看网站目录下的conn.asp 得到SQL的用户名密码,再登陆进SQL执行添加用户,提管理员权限。也可以抓SERV-U下的ServUDaemon.ini修改后上传,得到系统管理 员权限。还可以传本地溢出SERV-U的工具直接添加用户到Administrators等等。大家可以看到,一旦黑客找到了切入点,在没有权限限制的情 况下,黑客将一帆风顺的取得管理员权限。
那我们现在就来看看Windows 2000的默认权限设置到底是怎样的。对于各个卷的根目录,默认给了Everyone组完全控制权。这意味着任何进入电脑的 用户将不受限制的在这些根目录中为所欲为。系统卷下有三个目录比较特殊,系统默认给了他们有限制的权限,这三个目录是Documents and settings、Program files和Winnt。对于Documents and settings,默认的权限是这样分配的:Administrators拥有完全控制权;Everyone拥有读&运,列和读权限;Power users拥有读&运,列和读权限;SYSTEM同Administrators;Users拥有读&运,列和读权限。对于 Program files,Administrators拥有完全控制权;Creator owner拥有特殊权限;Power users有完全控制权;SYSTEM同Administrators;Terminal server users拥有完全控制权,Users有读&运,列和读权限。对于Winnt,Administrators拥有完全控制权;Creator owner拥有特殊权限;Power users有完全控制权;SYSTEM同Administrators;Users有读&运,列和读权限。而非系统卷下的所有目录都将继承其父目 录的权限,也就是Everyone组完全控制权!
现在大家知道为什么我们刚刚在测试的时候能一帆风顺的取得管理员权限了吧?权限设置的太低了!一个人在访问网站的时候,将被自动赋予IUSR 用户,它是隶属于Guest组的。本来权限不高,但是系统默认给的Everyone组完全控制权却让它“身价倍增”,到最后能得到 Administrators了。那么,怎样设置权限给这台WEB服务器才算是安全的呢?大家要牢记一句话:“最少的服务+最小的权限=最大的安全”对于 服务,不必要的话一定不要装,要知道服务的运行是SYSTEM级的哦,对于权限,本着够用就好的原则分配就是了。对于WEB服务器,就拿刚刚那台服务器来说,我是这样设置权限的,大家可以参考一下:各个卷的根目录、Documents and settings以及Program files,只给Administrator完全控制权,或者干脆直接把Program files给删除掉;给系统卷的根目录多加一个Everyone的读、写权;给e:\www目录,也就是网站目录读、写权。
最后,还要把cmd.exe这个文件给挖出来,只给Administrator完全控制权。经过这样的设置后,再想通过我刚刚的方法入侵这台 服务器就是不可能完成的任务了。可能这时候又有读者会问:“为什么要给系统卷的根目录一个Everyone的读、写权?网站中的ASP文件运行不需要运行 权限吗?”问的好,有深度。是这样的,系统卷如果不给Everyone的读、写权的话,启动计算机的 时候,计算机会报错,而且会提示虚拟内存不足。当然这也有个前提----虚拟内存是分配在系统盘的,如果把虚拟内存分配在其他卷上,那你就要给那个卷 Everyone的读、写权。ASP文件的运行方式是在服务器上执行,只把执行的结果传回最终用户的浏览器,这没错,但ASP文件不是系统意义上的可执行 文件,它是由WEB服务的提供者----IIS来解释执行的,所以它的执行并不需要运行的权限。
经过上面的讲解以后,你一定对权限有了一个初步了了解了吧?想更深入的了解权限,那么权限的一些特性你就不能不知道了,权限是具有继承性、累加性 、优先性、交叉性的。
继承性是说下级的目录在没有经过重新设置之前,是拥有上一级目录权限设置的。这里还有一种情况要说明一下,在分区内复制目录或文件的时候,复 制过去的目录和文件将拥有它现在所处位置的上一级目录权限设置。但在分区内移动目录或文件的时候,移动过去的目录和文件将拥有它原先的权限设置。
累加是说如一个组GROUP1中有两个用户USER1、USER2,他们同时对某文件或目录的访问权限分别为“读取”和“写入”,那么组 GROUP1对该文件或目录的访问权限就为USER1和USER2的访问权限之和,实际上是取其最大的那个,即“读取”+“写入”=“写入”。 又如一个用户USER1同属于组GROUP1和GROUP2,而GROUP1对某一文件或目录的访问权限为“只读”型的,而GROUP2对这一文件或文件 夹的访问权限为“完全控制”型的,则用户USER1对该文件或文件夹的访问权限为两个组权限累加所得,即:“只读”+“完全控制”=“完全控制”。
优先性,权限的这一特性又包含两种子特性,其一是文件的访问权限优先目录的权限,也就是说文件权限可以越过目录的权限,不顾上一级文件夹的设 置。另一特性就是“拒绝”权限优先其它权限,也就是说“拒绝”权限可以越过其它所有其它权限,一旦选择了“拒绝”权限,则其它权限也就不能取任何作用,相 当于没有设置。
交叉性是指当同一文件夹在为某一用户设置了共享权限的同时又为用户设置了该文件夹的访问权限,且所设权限不一致时,它的取舍原则是取两个权限 的交集,也即最严格、最小的那种权限。如目录A为用户USER1设置的共享权限为“只读”,同时目录A为用户USER1设置的访问权限为“完全控制”,那 用户USER1的最终访问权限为“只读”。
权限设置的问题我就说到这了,在最后我还想给各位读者提醒一下,权限的设置必须在NTFS分区中才能实现的,FAT32是不支持权限设置的。同时还想给各位管理员们一些建议:
1.养成良好的习惯,给服务器硬盘分区的时候分类明确些,在不使用服务器的时候将服务器锁定,经常更新各种补丁和升级杀毒软件。
2.设置足够强度的密码,这是老生常谈了,但总有管理员设置弱密码甚至空密码。
3.尽量不要把各种软件安装在默认的路径下
4.在英文水平不是问题的情况下,尽量安装英文版操作系统。
5.切忌在服务器上乱装软件或不必要的服务。
6.牢记:没有永远安全的系统,经常更新你的知识。
概述
Windows不像类UNIX系统一样开源,而且Microsoft也不可能公布基于Windows的源代码.所以,对于早期的Windows版本来 将,企业很难满足Windows的安全现状,因为他们不能根据自己的企业来制定满主自己的解决方案.但是,不断完善的Windows给我们带来的越来越好 的安全性,以及Windows的高部署性给越来越多的企业带来的惊喜,越来越多的企业选择了Windows,也越来越多的人开始研究Windows.包括 Windows在企业的部署以及Windows的安全性和可扩展性.从Widows NT以来,Microsoft在Windows的安全方面做了很多,最典型的就包括NTFS文件系统.而且结合Microsoft ISA Server可以让企业的安全性大大提升.本文就Windows现有的安全状况加以分析,让更多的从事Windows管理的专业人员提供更深入的对 Windows的安全了解.
操作系统安全定义
无论任何操作系统(OS),都有一套规范的、可扩展的安全定义。从计算机的访问到用户策略等。操作系统的安全定义包括5大类,分别为:身份认证、访问控制、数据保密性、数据完整性以及不可否认性。
身份认证
最基本的安全机制。当用户登陆到计算机操作系统时,要求身份认证,最常见的就是使用帐号以及密钥确认身份。但由于该方法的局限性,所以当计算机出现漏洞或 密钥泄漏时,可能会出现安全问题。其他的身份认证还有:生物测定(compaq的鼠标认证)指纹、视网模等。这几种方式提供高机密性,保护用户的身份验 证。采用唯一的方式,例如指纹,那么,恶意的人就很难获得除自己之外在有获得访问权限。
访问控制
在WINDOWS NT之后的WINDOWS版本,访问控制带来的更加安全的访问方法。该机制包括很多内容,包括磁盘的使用权限,文件夹的权限以及文件权限继承等。最常见的 访问控制可以属WINDOWS的NTFS文件系统了。自从NTFS出现后,很多人都从FAT32转向NTFS,提供更加安全的访问控制机制。
数据保密性
处于企业中的服务器数据的安全性对于企业来讲,决定着企业的存亡。加强数据的安全性是每个企业都需考虑的。从数据的加密方式,以及数据的加密算法,到用户 对公司内部数据的保密工作。我们最常见的是采用加密算法进行加密。在通信中,我们最常见的有SSL2.0加密,数据以及其他的信息采用MD5等。虽然 MD5的加密算法已经被破解,但是MD5的安全性依然能后保证数据的安全。
数据完整性
在文件传输中,我们更多考虑的是数据的完整性。虽然这也算数据的保密性的范畴,但是,这是无法防范的。在数据的传输中,可能就有象HACKER的人在监听 或捕获您的数据,然后破解您数据的加密算法,从而得到重要的信息,包括用户帐号密码等。所以,完整性我们更多的考虑到加密算法的安全性以及可靠性。公钥私 钥就是最好的例子。
不可否认性
根据《*公共安全行业标准》的计算机信息系统安全产品部件的规范,验证发送方信息发送和接收方信息接收的不可否认性。在不可否认性鉴别过程中 用于信息发布方和接收方的不可否认性鉴别的信息。验证信息发送方和接收方的不可否认性的过程。对双方的不可否认性鉴别信息需进行审计跟踪。
信息发送者的不可否认性鉴别信息必须是不可伪造的;
信息接收者的不可否认性鉴别信息必须是不可伪造的。
注意
信息安全的五类服务,作为安全的操作系统时必须提供的。
有些操作系统所提供的服务是不健全的、默认关闭的。
信息安全评估标准
本节我将带大家了解信息安全评估标准,关于该标准,其实很多国家都制定了相关的标准,我在这向大家介绍以下几种标准:
美国TCSEC(桔皮书)
该标准是美国国防部制定的。它将安全分为4个方面:安全政策、可说明性、安全保障和文档。在美国国防部虹系列(Rainbow Series)标准中有详细的描述。该标准将以上4个方面分为7个安全级别,从低到高依次为D、C1、C2、B1、B2、B3和A级。
欧洲ITSEC
与TCSEC不同,它并不把保密措施直接与计算机功能相联系,而是只叙述技术安全的要求,把保密作为安全增强功能。另外,TCSEC把保密作为安全的重 点,而ITSEC则把完整性、可用性与保密性作为同等重要的因素。ITSEC定义了从E0级(不满足品质)到E6级(形式化验证)的7个安全等级,对于每 个系统,安全功能可分别定义。ITSEC预定义了10种功能,其中前5种与桔皮书中的C1~B3级非常相似。
加拿大CTCPEC
该标准将安全需求分为4个层次:机密性、完整性、可靠性和可说明性。
美国联邦准则(FC)
该标准参照了CTCPEC及TCSEC,其目的是提供TCSEC的升级版本,同时保护已有投资,但FC有很多缺陷,是一个过渡标准,后来结合ITSEC发展为联合公共准则。
联合公共准则(CC) Common Critical
CC的目的是想把已有的安全准则结合成一个统一的标准。该计划从1993年开始执行,1996年推出第一版,但目前仍未付诸实施。CC结合了FC及ITSEC的主要特征,它强调将安全的功能与保障分离,并将功能需求分为9类 63族,将保障分为7类 29族。
ISO安全体系结构标准
在安全体系结构方面,ISO制定了国际标准ISO7498-2-1989《信息处理系统开放系统互连基本参考模型第2部分安全体系结构》。该标准为开 放系统互连(OSI)描述了基本参考模型,为协调开发现有的与未来的系统互连标准建立起了一个框架。其任务是提供安全服务与有关机制的一般描述,确定在参 考模型内部可以提供这些服务与机制的位置。
国内安全标准、政策制定和实施情况
中国*部主持制定、国家技术标准局发布的*国家标准GB17895-1999《计算机信息系统安全保护等级划分准则》已经正式颁布,并将于 2001年1月1日起实施。该准则将信息系统安全分为5个等级,分别是:自主保护级、系统审计保护级、安全标记保护级、结构化保护级和访问验证保护级。主 要的安全考核指标有身份认证、自主访问控制、数据完整性、审计、隐蔽信道分析、客体重用、强制访问控制、安全标记、可信路径和可信恢复等,这些指标涵盖了 不同级别的安全要求。
另外还有《信息处理系统开放系统互联基本参考模型第2部分安全体系结构》(GB/T 9387.2 1995)、《信息处理数据加密实体鉴别机制第I部分:一般模型》(GB 15834.1-1995)、《信息技术设备的安全》(GB 4943-1995)等。
TCSEC定义的内容
美国TCSEC(桔皮书)的7个安全级别,从低到高依次为D、C1、C2、B1、B2、B3和A级。我们分别来介绍下:
A级-校验级保护,提供低级别手段
|
B3级-安全域,数据隐藏与分层、屏蔽
|
B2级-结构化内容保护,支持硬件保护
|
B1级-标记安全保护,如System V等
|
C2级-有自主的访问安全性,区分用户
|
C1级-不区分用户,基本的访问控制
|
D级-没有安全性可言,例如MS DOS
Windows系统的安全架构
Windows系统采用金字塔型的安全架构,相信大家都应该听说过。对于金字塔性安全架构来讲下面的最重要,定义完整直观的安全策略是最重要。如图一
图一:Windows金字塔安全架构
Audit 审计 | Administration 管理
Encryption 加密 | Access Control 访问控制
User Authentication 证明, 鉴定
Windows系统的安全组件
对于Windows系统来讲,系统的安全性主要体现在系统的组件的功能上。Windows提供5个安全组件,保障了系统的安全性。Windows系统组件体现在很多方面,例如Windows用户策略,访问控制的判断,对象的重用,强制登陆等。
访问控制的判断(Discretion access control)
如图一所示,访问控制是在第二层上,安全性级别为普通。访问控制的判断允许对象所有者可以控制谁被允许访问该对象以及访问的方式。
对象重用(Object reuse)
如果您正在阅读一篇本地的文章,例如是DOC文件,那么,在你阅读的同时,又想将该文件打包传送给其他人,这时候进行打包的工作是不被允许的,当你执行该 工作时,系统回提示您该文件正在被另一个程序所使用。当资源(内存、磁盘等)被某应用访问时,Windows 禁止所有的系统应用访问该资源,这也就是为什么无法恢复已经被删除的文件的原因。
强制登陆(Mandatory log on)
该类方式运用的最多的地方该属活动目录(Active Directory),如果在域控制器(DC)限制用户登陆的方式,要求所有的用户必须登陆,那么通过认证后才可以访问系统资源。
审核(Auditing)
打开管理工具种的“事件查看器”,点击“安全性”,这里就可以看到本地计算机登陆的审核列表。如果登陆该计算机失败,管理员就很快可以确定是否时恶意入侵等其他信息。在控制用户访问资源的同时,也可以对这些访问作了相应的记录。
对象的访问控制(Control of access to object)
在NTFS文件系统种,对方访问控制做得非常的到位。选择一个文件夹单击右键选择“属性”,在“安全”选项里可以看到用户所具有的权限值。NTFS文件系 统很好的解决了多用户对资源的特级访问权限。要访问资源,必须是该资源允许被访问,然后是用户或应用通过第一次认证后再访问。
Windows安全子系统的组件
安全标识符(Security Identifiers)
对于SID,大家一定不会陌生。简单来说就是每次当我们创建一个用户或一个组的时候,系统会分配给改用户或组一个唯一SID,当你重新安装系统后,也会得到一个唯一的SID。SID是唯一的,不随用户的删除而分配到另外的用户使用。
请记住,SID永远都是唯一的SIF是由计算机名、当前时间、当前用户态线程的CPU耗费时间的总和三个参数决定以保证它的唯一性。
例: S-1-5-21-1763234323-3212657521-1234321321-500
访问令牌(Access tokens)
在很多网络培训教程都有详细的介绍。当用户通过验证后,登陆进程会给用户一个访问令牌,该令牌相当于用户访问系统资源的票证,当用户试图访问系统资源时, 将访问令牌提供给Windows 系统,然后Windows NT检查用户试图访问对象上的访问控制列表。如果用户被允许访问该对象,系统将会分配给用户适当的访问权限。
访问令牌是用户在通过验证的时候有登陆进程所提供的,所以改变用户的权限需要注销后重新登陆,重新获取访问令牌。
安全描述符(Security descriptors)
Windows 系统中的任何对象的属性都有安全描述符这部分。它保存对象的安全配置。
访问控制列表(Access control lists)
访问控制列表有两种:任意访问控制列表(Discretionary ACL)、系统访问控制列表(System ACL)。任意访问控制列表包含了用户和组的列表,以及相应的权限,允许或拒绝。每一个用户或组在任意访问控制列表中都有特殊的权限。而系统访问控制列表 是为审核服务的,包含了对象被访问的时间。
访问控制项(Access control entries)
访问控制项(ACE)包含了用户或组的SID以及对象的权限。访问控制项有两种:允许访问和拒绝访问。拒绝访问的级别高于允许访问。
Windows安全子系统
Winlogon 加载GINA,监视认证顺序
Graphical Identification and Authentication DLL (GINA) 提供登陆接口
Local Security Authority(LSA) 加载认证包
Security Support Provider Interface(SSPI) Microsoft安全支持提供器接口
Authentication Packages 提供真正的用户校验
Security support providers 支持额外的验证机制
Netlogon Service 管理用户和用户证书的数据库
Security Account Manager(SAM) 为认证建立安全通道
该流程过程如图二:
图二
Winlogon and Gina
Winlogon调用GINA DLL,并监视安全认证序列。而GINA DLL提供一个交互式的界面为用户登陆提供认证请求。GINA DLL被设计成一个独立的模块,当然我们也可以用一个更加强有力的认证方式(指纹、视网膜)替换内置的GINA DLL。
Winlogon在注册表\HKLM\Software\Microsoft\WindowsNT\CurrentVersion\Winlogon ,如果存在GinaDLL键,Winlogon将使用这个DLL,如果不存在该键,Winlogon将使用默认值MSGINA.DLL
本地安全认证(Local Security Authority)
本地安全认证(LSA)是一个被保护的子系统,它负责以下任务:
调用所有的认证包,检查在注册表\HKLM\SYSTEM\CurrentControlSet\Control\LSA下 AuthenticationPAckages下的值,并调用该DLL进行认证(MSV_1.DLL)。在4.0版里,Windows NT会寻找\HKLM\SYSTEM\CurrentControlSet\Control\LSA 下所有存在的SecurityPackages值并调用。
重新找回本地组的SIDs和用户的权限。
创建用户的访问令牌。
管理本地安装的服务所使用的服务账号。
储存和映射用户权限。
管理审核的策略和设置。
管理信任关系。
安全支持提供者的接口(Security Support Provide Interface)
微软的Security Support Provide Interface很简单地遵循RFC 2743和RFC 2744的定义,提供一些安全服务的API,为应用程序和服务提供请求安全的认证连接的方法。
认证包(Authentication Package)
认证包可以为真实用户提供认证。通过GINA DLL的可信认证后,认证包返回用户的SIDs给LSA,然后将其放在用户的访问令牌中。
安全支持提供者(Security Support Provider)
安全支持提供者是以驱动的形式安装的,能够实现一些附加的安全机制,默认情况下,Windows NT安装了以下三种:
Msnsspc.dll:微软网络挑战/反应认证模块
Msapsspc.dll:分布式密码认证挑战/反应模块,该模块也可以在微软网络中使用
Schannel.dll:该认证模块使用某些证书颁发机构提供的证书来进行验证,常见的证书机构比如Verisign。这种认证方式经常在使用 SSL(Secure Sockets Layer)和PCT(Private Communication Technology)协议通信的时候用到。
网络登陆(Netlogon)
网络登陆服务必须在通过认证后建立一个安全的通道。要实现这个目标,必须通过安全通道与域中的域控制器建立连接,然后,再通过安全的通道传递用户的口令,在域的域控制器上响应请求后,重新取回用户的SIDs和用户权限。
安全账号管理者(Security Account Manager)
安全账号管理者,也就是我们经常所说的SAM,它是用来保存用户账号和口令的数据库。保存了注册表中\HKLM\Security\Sam中的一部分内容。不同的域有不同的Sam,在域复制的过程中,Sam包将会被拷贝。
提高系统的安全性的一些建议
本节我将向大家介绍如何提高系统的安全性。该节我不做具体的分析,我就现在的Windows系统的安全性做个归纳,然后根据这些安全性做一些配置。
网络的安全性
网络的安全性是系统安全性的基础,简单的说就是系统的安全性是建立在网络安全性之上的。提高网络的安全性才能加强系统的安全性,否则就是一张废纸。
网络安全性主要有:网络边界的安全性,网络架构和合理性,网络环境的合理性,网络传输数据的安全性,路由策略等等。加强边界和内网与公网之间的安全性是非常重要的,找决定着网络的安全架构。一般我们将内网与外网用防火墙隔开,限制了外部网络与内部网络的通信。
解决方案:可以选用优良的路由设备以及网络设备,选用优秀的网络防火墙,设定好自己的网络访问策略。例如使用Microsoft ISA Server
系统的安全性
如果您的网络中使用了ISA Server的话,那么有一个非常棘手的问题,就是ISA Server不对内部网络发起的攻击做任何处理。那么,你应该加强内部网络的安全性,提高用户的安全意识等。
系统的安全性我在上面已经提到了,包括用户策略,访问策略等等。建议您去参考一些部署方面的文章或书籍,配合自己的网络环境做好安全工作。
数据的安全性
对于数据的安全性,我们要做的就是对数据进行加密,在网络中,我们可以采用很多的加密方式,这论您的环境来做。
总结
每个管理人员都遇到过安全问题,那么,了解系统的安全机制会让您部署更多的安全策略,提高网络及系统的安全性。本文主要介绍的是Windows操作系统的安全机制和一些安全方面的讨论,希望大家能从中获得更多东西。
本篇文章内容主要参考了 陈皓 《以程序的方式操纵NTFS的文件权限 》,然后加入了一些自己学习和理解的东西。
在NTFS文件系统出现后,在Windows系统(2K/XP/Vista..)下的对象,包括文件系统,进程、命名管道、打印机、网络共享、或是注册表等等,都可以设置用户访问权限。
在Windows系统中,其是用一个安全描述符(Security Descriptors)的结构来保存其权限的设置信息,简称为SD,其在Windows SDK中的结构名是“SECURITY_DESCRIPTOR”,这是包括了安全设置信息的结构体,其结构体内容定义如下:
typedef struct _SECURITY_DESCRIPTOR {
UCHAR Revision;
UCHAR Sbz1;
SECURITY_DESCRIPTOR_CONTROL Control;
PSID Owner;
PSID Group;
PACL Sacl;
PACL Dacl;
} SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;
一个安全描述符包含以下安全信息:
- 两个安全标识符(Security identifiers),简称为SID,分别是OwnerSid和GroupSid. 所谓SID就是每次当我们创建一个用户或一个组的时候,系统会分配给改用户或组一个唯一SID,当你重新安装系统后,也会得到一个唯一的SID。SID是唯一的,不随用户的删除而分配到另外的用户使用。
请记住,SID永远都是唯一的SIF是由计算机名、当前时间、当前用户态线程的CPU耗费时间的总和三个参数决定以保证它的唯一性。
例: S-1-5-21-1763234323-3212657521-1234321321-500 - 一个DACL(Discretionary Access Control List),其指出了允许和拒绝某用户或用户组的存取控制列表。 当一个进程需要访问安全对象,系统就会检查DACL来决定进程的访问权。如果一个对象没有DACL,那么就是说这个对象是任何人都可以拥有完全的访问权限。
- 一个SACL(System Access Control List),其指出了在该对象上的一组存取方式(如,读、写、运行等)的存取控制权限细节的列表。
- 还有其自身的一些控制位。SECURITY_DESCRIPTOR_CONTROL
DACL和SACL构成了整个存取控制列表Access Control List,简称ACL,ACL中的每一项,我们叫做ACE(Access Control Entry),ACL中的每一个ACE。
ACL结构体的内容如下:
typedef struct _ACL {
BYTE AclRevision;
BYTE Sbz1;
WORD AclSize;
WORD AceCount;
WORD Sbz2;
} ACL;
typedef ACL *PACL;
我们的程序不用直接维护SD这个结构,这个结构由系统维护。我们只用使用Windows 提供的相关的API函数来取得并设置SD中的信息就行了。不过这些API函数只有Windows NT/2K/XP才支持。
Windows提供了一系列的安全信息的存取,控制函数,如 GetNamedSecurityInfo, SetNamedSecurityInfo,GetSecurityInfo, SetSecurityInfo等。
下图说明了,安全对象和DACL以及访问者之间的联系(来源于MSDN)。注意,DACL表中的每个ACE的顺序是有意义的,如果前面的Allow(或denied)ACE通过了,那么,系统就不会检查后面的ACE了。另外"拒绝"权限一般是优先于其他权限的。ACE在DACL中的顺序非常重要。
系统会按照顺序依次检查所有的ACE规则,如下面的条件满足,则退出:
1、 如果一个Access-Denied的ACE明显地拒绝了请求者。
2、 如果某Access-Allowed的ACE明显地同意了请求者。
3、 全部的ACE都检查完了,但是没有一条ACE明显地允许或是拒绝请求者,那么系统将使用默认值,拒绝请求者的访问。
更多的理论和描述,请参看MSDN。
实例例程
主要实现2个功能,获取目录的安全设置和为目录增加一个安全设置项(该程序来源于MSDN,原作者 陈皓稍作修改,并加入了注解,我也稍作了点修改)
1、 对于文件、目录、命令管道,我们不一定要使用GetNamedSecurityInfo和SetNamedSecurityInfo函数,我们可以使用其专用函数GetFileSecurity和SetFileSecurity函数来取得或设置文件对象的SD,以设置其访问权限。需要使用这两个函数并不容易,正如前面我们所说的,我们还需要处理SD参数,要处理SD,就需要处理DACL和ACE,以及用户的相关SID,于是,一系统列的函数就被这两个函数带出来了。
2、 对于上一个例子中的使用硬编码指定SID的处理方法是。调用LookupAccountName函数时,先把SID,Domain名的参数传为空NULL,于是LookupAccountName会返回用户的SID的长度和Domain名的长度,于是你可以根据这个长度分配内存,然后再次调用LookupAccountName函数。于是就可以达到到态分配内存的效果。对于ACL也一样。
3、 对于给文件的ACL中增加一个ACE条目,一般的做法是先取出文件上的ACL,逐条取出ACE,和现需要增加的ACE比较,如果有冲突,则删除已有的ACE,把新加的ACE添置到最后。这里的最后,应该是非继承而来的ACE的最后。关于ACL继承,NTFS中,你可以设置文件和目录是否继承于其父目录的设置。在程序中同样可以设置。
还是请看例程,这个程序比较长,来源于MSDN,我做了一点点修改,并把自己的理解加在注释中,所以,请注意代码中的注释:
// SetSD.cpp :
//
#include <stdio.h>
#include <bitset>
#include <tchar.h>
#include <windows.h>
#include <string>
#include <iostream>
using std::bitset;
using std::string;
using std::cout;
using std::endl;
//使用Windows的HeapAlloc函数进行动态内存分配
#define myheapalloc(x) (HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, x))
#define myheapfree(x) (HeapFree(GetProcessHeap(), 0, x))
typedef BOOL (WINAPI *SetSecurityDescriptorControlFnPtr)(
IN PSECURITY_DESCRIPTOR pSecurityDescriptor,
IN SECURITY_DESCRIPTOR_CONTROL ControlBitsOfInterest,
IN SECURITY_DESCRIPTOR_CONTROL ControlBitsToSet);
typedef BOOL (WINAPI *AddAccessAllowedAceExFnPtr)(
PACL pAcl,
DWORD dwAceRevision,
DWORD AceFlags,
DWORD AccessMask,
PSID pSid
);
//【接口】
// BOOL AddAccessRights(TCHAR *lpszFileName, TCHAR *lpszAccountName, DWORD dwAccessMask)
//【机能概要】
// 为文件(目录)添加一个帐户(组)的权限
//【入力】
// TCHAR *lpszFileName 文件(目录)
// TCHAR *lpszAccountName 帐户(组)
// DWORD dwAccessMask 权限设置(如GENERIC_ALL,GENERIC_READ等)
//【输出】
// 无
//【输入输出】
// 无
//【返回值】
// BOOL
//【例外】
// 无
//---------------------------------------------------------------------------
BOOL AddAccessRights(TCHAR *lpszFileName, TCHAR *lpszAccountName, DWORD dwAccessMask) {
// 声明SID变量
SID_NAME_USE snuType;
// 声明和LookupAccountName相关的变量(注意,全为0,要在程序中动态分配)
TCHAR * szDomain = NULL;
DWORD cbDomain = 0;
LPVOID pUserSID = NULL;
DWORD cbUserSID = 0;
// 和文件相关的安全描述符 SD 的变量
PSECURITY_DESCRIPTOR pFileSD = NULL; // 结构变量
DWORD cbFileSD = 0; // SD的size
// 一个新的SD的变量,用于构造新的ACL(把已有的ACL和需要新加的ACL整合起来)
SECURITY_DESCRIPTOR newSD;
// 和ACL 相关的变量
PACL pACL = NULL;
BOOL fDaclPresent;
BOOL fDaclDefaulted;
ACL_SIZE_INFORMATION AclInfo;
// 一个新的 ACL 变量
PACL pNewACL = NULL; //结构指针变量
DWORD cbNewACL = 0; //ACL的size
// 一个临时使用的 ACE 变量
LPVOID pTempAce = NULL;
UINT CurrentAceIndex = 0; //ACE在ACL中的位置
UINT newAceIndex = 0; //新添的ACE在ACL中的位置
//API函数的返回值,假设所有的函数都返回失败。
BOOL fResult = FALSE;
BOOL fAPISuccess = FALSE;
SECURITY_INFORMATION secInfo = DACL_SECURITY_INFORMATION;
// 下面的两个函数是新的API函数,仅在Windows 2000以上版本的操作系统支持。
// 在此将从Advapi32.dll文件中动态载入。如果你使用VC++ 6.0编译程序,而且你想
// 使用这两个函数的静态链接。则请为你的编译加上:/D_WIN32_WINNT=0x0500
// 的编译参数。并且确保你的SDK的头文件和lib文件是最新的。
SetSecurityDescriptorControlFnPtr _SetSecurityDescriptorControl = NULL;
AddAccessAllowedAceExFnPtr _AddAccessAllowedAceEx = NULL;
__try {
//
// STEP 1: 通过用户名取得SID
// 在这一步中LookupAccountName函数被调用了两次,第一次是取出所需要
// 的内存的大小,然后,进行内存分配。第二次调用才是取得了用户的帐户信息。
// LookupAccountName同样可以取得域用户或是用户组的信息。(请参看MSDN)
//
fAPISuccess = LookupAccountName(NULL, lpszAccountName,
pUserSID, &cbUserSID, szDomain, &cbDomain, &snuType);
// 以上调用API会失败,失败原因是内存不足。并把所需要的内存大小传出。
// 下面是处理非内存不足的错误。
if (fAPISuccess)
__leave;
else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
_tprintf(TEXT("LookupAccountName() failed. Error %d\n"),
GetLastError());
__leave;
}
pUserSID = myheapalloc(cbUserSID);
if (!pUserSID) {
_tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError());
__leave;
}
szDomain = (TCHAR *) myheapalloc(cbDomain * sizeof(TCHAR));
if (!szDomain) {
_tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError());
__leave;
}
fAPISuccess = LookupAccountName(NULL, lpszAccountName,
pUserSID, &cbUserSID, szDomain, &cbDomain, &snuType);
if (!fAPISuccess) {
_tprintf(TEXT("LookupAccountName() failed. Error %d\n"),
GetLastError());
__leave;
}
//
// STEP 2: 取得文件(目录)相关的安全描述符SD
// 使用GetFileSecurity函数取得一份文件SD的拷贝,同样,这个函数也
// 是被调用两次,第一次同样是取SD的内存长度。注意,SD有两种格式:自相关的
// (self-relative)和 完全的(absolute),GetFileSecurity只能取到“自
// 相关的”,而SetFileSecurity则需要完全的。这就是为什么需要一个新的SD,
// 而不是直接在GetFileSecurity返回的SD上进行修改。因为“自相关的”信息
// 是不完整的。
fAPISuccess = GetFileSecurity(lpszFileName,
secInfo, pFileSD, 0, &cbFileSD);
// 以上调用API会失败,失败原因是内存不足。并把所需要的内存大小传出。
// 下面是处理非内存不足的错误。
if (fAPISuccess)
__leave;
else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
_tprintf(TEXT("GetFileSecurity() failed. Error %d\n"),
GetLastError());
__leave;
}
pFileSD = myheapalloc(cbFileSD);
if (!pFileSD) {
_tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError());
__leave;
}
fAPISuccess = GetFileSecurity(lpszFileName,
secInfo, pFileSD, cbFileSD, &cbFileSD);
if (!fAPISuccess) {
_tprintf(TEXT("GetFileSecurity() failed. Error %d\n"),
GetLastError());
__leave;
}
//
// STEP 3: 初始化一个新的SD
//
if (!InitializeSecurityDescriptor(&newSD,
SECURITY_DESCRIPTOR_REVISION)) {
_tprintf(TEXT("InitializeSecurityDescriptor() failed.")
TEXT("Error %d\n"), GetLastError());
__leave;
}
//
// STEP 4: 从GetFileSecurity 返回的SD中取DACL
//
if (!GetSecurityDescriptorDacl(pFileSD, &fDaclPresent, &pACL,
&fDaclDefaulted)) {
_tprintf(TEXT("GetSecurityDescriptorDacl() failed. Error %d\n"),
GetLastError());
__leave;
}
//
// STEP 5: 取 DACL的内存size
// GetAclInformation可以提供DACL的内存大小。只传入一个类型为
// ACL_SIZE_INFORMATION的structure的参数,需DACL的信息,是为了
// 方便我们遍历其中的ACE。
AclInfo.AceCount = 0; // Assume NULL DACL.
AclInfo.AclBytesFree = 0;
AclInfo.AclBytesInUse = sizeof(ACL);
if (pACL == NULL)
fDaclPresent = FALSE;
// 如果DACL不为空,则取其信息。(大多数情况下“自关联”的DACL为空)
if (fDaclPresent) {
if (!GetAclInformation(pACL, &AclInfo,
sizeof(ACL_SIZE_INFORMATION), AclSizeInformation)) {
_tprintf(TEXT("GetAclInformation() failed. Error %d\n"),
GetLastError());
__leave;
}
}
//
// STEP 6: 计算新的ACL的size
// 计算的公式是:原有的DACL的size加上需要添加的一个ACE的size,以
// 及加上一个和ACE相关的SID的size,最后减去两个字节以获得精确的大小。
cbNewACL = AclInfo.AclBytesInUse + sizeof(ACCESS_ALLOWED_ACE)
+ GetLengthSid(pUserSID) - sizeof(DWORD);
//
// STEP 7: 为新的ACL分配内存
//
pNewACL = (PACL) myheapalloc(cbNewACL);
if (!pNewACL) {
_tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError());
__leave;
}
//
// STEP 8: 初始化新的ACL结构
//
if (!InitializeAcl(pNewACL, cbNewACL, ACL_REVISION2)) {
_tprintf(TEXT("InitializeAcl() failed. Error %d\n"),
GetLastError());
__leave;
}
//
// STEP 9 如果文件(目录) DACL 有数据,拷贝其中的ACE到新的DACL中
//
// 下面的代码假设首先检查指定文件(目录)是否存在的DACL,如果有的话,
// 那么就拷贝所有的ACE到新的DACL结构中,我们可以看到其遍历的方法是采用
// ACL_SIZE_INFORMATION结构中的AceCount成员来完成的。在这个循环中,
// 会按照默认的ACE的顺序来进行拷贝(ACE在ACL中的顺序是很关键的),在拷
// 贝过程中,先拷贝非继承的ACE(我们知道ACE会从上层目录中继承下来)
//
newAceIndex = 0;
if (fDaclPresent && AclInfo.AceCount) {
for (CurrentAceIndex = 0;
CurrentAceIndex < AclInfo.AceCount;
CurrentAceIndex++) {
//
// STEP 10: 从DACL中取ACE
//
if (!GetAce(pACL, CurrentAceIndex, &pTempAce)) {
_tprintf(TEXT("GetAce() failed. Error %d\n"),
GetLastError());
__leave;
}
//
// STEP 11: 检查是否是非继承的ACE
// 如果当前的ACE是一个从父目录继承来的ACE,那么就退出循环。
// 因为,继承的ACE总是在非继承的ACE之后,而我们所要添加的ACE
// 应该在已有的非继承的ACE之后,所有的继承的ACE之前。退出循环
// 正是为了要添加一个新的ACE到新的DACL中,这后,我们再把继承的
// ACE拷贝到新的DACL中。
//
if (((ACCESS_ALLOWED_ACE *)pTempAce)->Header.AceFlags
& INHERITED_ACE)
break;
//
// STEP 12: 检查要拷贝的ACE的SID是否和需要加入的ACE的SID一样,
// 如果一样,那么就应该废掉已存在的ACE,也就是说,同一个用户的存取
// 权限的设置的ACE,在DACL中应该唯一。这在里,跳过对同一用户已设置
// 了的ACE,仅是拷贝其它用户的ACE。
//
if (EqualSid(pUserSID,
&(((ACCESS_ALLOWED_ACE *)pTempAce)->SidStart)))
{
ACCESS_ALLOWED_ACE pTempAce2 = *(ACCESS_ALLOWED_ACE *)pTempAce;
ACCESS_DENIED_ACE pTempAce4 = *(ACCESS_DENIED_ACE *)pTempAce;
int a = -1;
if (pTempAce2.Header.AceType == ACCESS_ALLOWED_ACE_TYPE)
{
a = 0;
}
else if (pTempAce2.Header.AceType == ACCESS_DENIED_ACE_TYPE)
{
a = 1;
}
else
a = 2;
continue;
}
//
// STEP 13: 把ACE加入到新的DACL中
// 下面的代码中,注意 AddAce 函数的第三个参数,这个参数的意思是
// ACL中的索引值,意为要把ACE加到某索引位置之后,参数MAXDWORD的
// 意思是确保当前的ACE是被加入到最后的位置。
//
if (!AddAce(pNewACL, ACL_REVISION, MAXDWORD, pTempAce,
((PACE_HEADER) pTempAce)->AceSize)) {
_tprintf(TEXT("AddAce() failed. Error %d\n"),
GetLastError());
__leave;
}
newAceIndex++;
}
}
//
// STEP 14: 把一个 access-allowed 的ACE 加入到新的DACL中
// 前面的循环拷贝了所有的非继承且SID为其它用户的ACE,退出循环的第一件事
// 就是加入我们指定的ACE。请注意首先先动态装载了一个AddAccessAllowedAceEx
// 的API函数,如果装载不成功,就调用AddAccessAllowedAce函数。前一个函数仅
// 在Windows 2000以后的版本支持,NT则没有,我们为了使用新版本的函数,我们首
// 先先检查一下当前系统中可不可以装载这个函数,如果可以则就使用。使用动态链接
// 比使用静态链接的好处是,程序运行时不会因为没有这个API函数而报错。
//
// Ex版的函数多出了一个参数AceFlag(第三人参数),用这个参数我们可以来设置一
// 个叫ACE_HEADER的结构,以便让我们所设置的ACE可以被其子目录所继承下去,而
// AddAccessAllowedAce函数不能定制这个参数,在AddAccessAllowedAce函数
// 中,其会把ACE_HEADER这个结构设置成非继承的。
//
_AddAccessAllowedAceEx = (AddAccessAllowedAceExFnPtr)
GetProcAddress(GetModuleHandle(TEXT("advapi32.dll")),
"AddAccessAllowedAceEx");
if (_AddAccessAllowedAceEx) {
if (!_AddAccessAllowedAceEx(pNewACL, ACL_REVISION2,
CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE ,
dwAccessMask, pUserSID)) {
_tprintf(TEXT("AddAccessAllowedAceEx() failed. Error %d\n"),
GetLastError());
__leave;
}
}else{
if (!AddAccessAllowedAce(pNewACL, ACL_REVISION2,
dwAccessMask, pUserSID)) {
_tprintf(TEXT("AddAccessAllowedAce() failed. Error %d\n"),
GetLastError());
__leave;
}
}
//
// STEP 15: 按照已存在的ACE的顺序拷贝从父目录继承而来的ACE
//
bitset<32> bit(dwAccessMask);
if (fDaclPresent && AclInfo.AceCount) {
for (;
CurrentAceIndex < AclInfo.AceCount;
CurrentAceIndex++) {
//
// STEP 16: 从文件(目录)的DACL中继续取ACE
//
if (!GetAce(pACL, CurrentAceIndex, &pTempAce)) {
_tprintf(TEXT("GetAce() failed. Error %d\n"),
GetLastError());
__leave;
}
//
// STEP 17: 把ACE加入到新的DACL中
//
if (!AddAce(pNewACL, ACL_REVISION, MAXDWORD, pTempAce,
((PACE_HEADER) pTempAce)->AceSize)) {
_tprintf(TEXT("AddAce() failed. Error %d\n"),
GetLastError());
__leave;
}
}
}
//
// STEP 18: 把新的ACL设置到新的SD中
//
if (!SetSecurityDescriptorDacl(&newSD, TRUE, pNewACL,
FALSE)) {
_tprintf(TEXT("SetSecurityDescriptorDacl() failed. Error %d\n"),
GetLastError());
__leave;
}
//
// STEP 19: 把老的SD中的控制标记再拷贝到新的SD中,我们使用的是一个叫
// SetSecurityDescriptorControl() 的API函数,这个函数同样只存在于
// Windows 2000以后的版本中,所以我们还是要动态地把其从advapi32.dll
// 中载入,如果系统不支持这个函数,那就不拷贝老的SD的控制标记了。
//
_SetSecurityDescriptorControl =(SetSecurityDescriptorControlFnPtr)
GetProcAddress(GetModuleHandle(TEXT("advapi32.dll")),
"SetSecurityDescriptorControl");
if (_SetSecurityDescriptorControl) {
SECURITY_DESCRIPTOR_CONTROL controlBitsOfInterest = 0;
SECURITY_DESCRIPTOR_CONTROL controlBitsToSet = 0;
SECURITY_DESCRIPTOR_CONTROL oldControlBits = 0;
DWORD dwRevision = 0;
if (!GetSecurityDescriptorControl(pFileSD, &oldControlBits,
&dwRevision)) {
_tprintf(TEXT("GetSecurityDescriptorControl() failed.")
TEXT("Error %d\n"), GetLastError());
__leave;
}
if (oldControlBits & SE_DACL_AUTO_INHERITED) {
controlBitsOfInterest =
SE_DACL_AUTO_INHERIT_REQ |
SE_DACL_AUTO_INHERITED ;
controlBitsToSet = controlBitsOfInterest;
}
else if (oldControlBits & SE_DACL_PROTECTED) {
controlBitsOfInterest = SE_DACL_PROTECTED;
controlBitsToSet = controlBitsOfInterest;
}
if (controlBitsOfInterest) {
if (!_SetSecurityDescriptorControl(&newSD,
controlBitsOfInterest,
controlBitsToSet)) {
_tprintf(TEXT("SetSecurityDescriptorControl() failed.")
TEXT("Error %d\n"), GetLastError());
__leave;
}
}
}
//
// STEP 20: 把新的SD设置设置到文件的安全属性中(千山万水啊,终于到了)
//
if (!SetFileSecurity(lpszFileName, secInfo,
&newSD)) {
_tprintf(TEXT("SetFileSecurity() failed. Error %d\n"),
GetLastError());
__leave;
}
fResult = TRUE;
} __finally {
//
// STEP 21: 释放已分配的内存,以免Memory Leak
//
if (pUserSID) myheapfree(pUserSID);
if (szDomain) myheapfree(szDomain);
if (pFileSD) myheapfree(pFileSD);
if (pNewACL) myheapfree(pNewACL);
}
return fResult;
}
//【接口】
// BOOL GetAccountRights(TCHAR *lpszFileName, TCHAR *lpszAccountName, int (&arrRights)[32])
//【机能概要】
// 获取该文件(目录)指定帐户(组)的权限
//【入力】
// TCHAR *lpszFileName 文件(目录)
// TCHAR *lpszAccountName 帐户(组)
// int (&arrRights)[32] 数组引用,要求传入参数必须是32个int数组
//【输出】
// 无
//【输入输出】
// 无
//【返回值】
// BOOL
//【例外】
// 无
//---------------------------------------------------------------------------
BOOL GetAccountRights(TCHAR *lpszFileName, TCHAR *lpszAccountName, int (&arrRights)[32]) {
//将参数arrRights初始化为0
for (int i = 0; i < 32; i++)
{
arrRights[i] = 0;
}
// 声明SID变量
SID_NAME_USE snuType;
// 声明和LookupAccountName相关的变量(注意,全为0,要在程序中动态分配)
TCHAR * szDomain = NULL;
DWORD cbDomain = 0;
LPVOID pUserSID = NULL;
DWORD cbUserSID = 0;
// 和文件相关的安全描述符 SD 的变量
PSECURITY_DESCRIPTOR pFileSD = NULL; // 结构变量
DWORD cbFileSD = 0; // SD的size
// 和ACL 相关的变量
PACL pACL = NULL;
BOOL fDaclPresent;
BOOL fDaclDefaulted;
ACL_SIZE_INFORMATION AclInfo;
// 一个临时使用的 ACE 变量
LPVOID pTempAce = NULL;
UINT CurrentAceIndex = 0; //ACE在ACL中的位置
//API函数的返回值,假设所有的函数都返回失败。
BOOL fResult = FALSE;
BOOL fAPISuccess = FALSE;
SECURITY_INFORMATION secInfo = DACL_SECURITY_INFORMATION;
__try {
//
// STEP 1: 通过用户名取得SID
// 在这一步中LookupAccountName函数被调用了两次,第一次是取出所需要
// 的内存的大小,然后,进行内存分配。第二次调用才是取得了用户的帐户信息。
// LookupAccountName同样可以取得域用户或是用户组的信息。(请参看MSDN)
//
fAPISuccess = LookupAccountName(NULL, lpszAccountName,
pUserSID, &cbUserSID, szDomain, &cbDomain, &snuType);
// 以上调用API会失败,失败原因是内存不足。并把所需要的内存大小传出。
// 下面是处理非内存不足的错误。
if (fAPISuccess)
__leave;
else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
_tprintf(TEXT("LookupAccountName() failed. Error %d\n"),
GetLastError());
__leave;
}
pUserSID = myheapalloc(cbUserSID);
if (!pUserSID) {
_tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError());
__leave;
}
szDomain = (TCHAR *) myheapalloc(cbDomain * sizeof(TCHAR));
if (!szDomain) {
_tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError());
__leave;
}
fAPISuccess = LookupAccountName(NULL, lpszAccountName,
pUserSID, &cbUserSID, szDomain, &cbDomain, &snuType);
if (!fAPISuccess) {
_tprintf(TEXT("LookupAccountName() failed. Error %d\n"),
GetLastError());
__leave;
}
//
// STEP 2: 取得文件(目录)相关的安全描述符SD
// 使用GetFileSecurity函数取得一份文件SD的拷贝,同样,这个函数也
// 是被调用两次,第一次同样是取SD的内存长度。注意,SD有两种格式:自相关的
// (self-relative)和 完全的(absolute),GetFileSecurity只能取到“自
// 相关的”,而SetFileSecurity则需要完全的。这就是为什么需要一个新的SD,
// 而不是直接在GetFileSecurity返回的SD上进行修改。因为“自相关的”信息
// 是不完整的。
fAPISuccess = GetFileSecurity(lpszFileName,
secInfo, pFileSD, 0, &cbFileSD);
// 以上调用API会失败,失败原因是内存不足。并把所需要的内存大小传出。
// 下面是处理非内存不足的错误。
if (fAPISuccess)
__leave;
else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
_tprintf(TEXT("GetFileSecurity() failed. Error %d\n"),
GetLastError());
__leave;
}
pFileSD = myheapalloc(cbFileSD);
if (!pFileSD) {
_tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError());
__leave;
}
fAPISuccess = GetFileSecurity(lpszFileName,
secInfo, pFileSD, cbFileSD, &cbFileSD);
if (!fAPISuccess) {
_tprintf(TEXT("GetFileSecurity() failed. Error %d\n"),
GetLastError());
__leave;
}
//
// STEP 3: 从GetFileSecurity 返回的SD中取DACL
//
if (!GetSecurityDescriptorDacl(pFileSD, &fDaclPresent, &pACL,
&fDaclDefaulted)) {
_tprintf(TEXT("GetSecurityDescriptorDacl() failed. Error %d\n"),
GetLastError());
__leave;
}
//
// STEP 4: 取 DACL的内存size
// GetAclInformation可以提供DACL的内存大小。只传入一个类型为
// ACL_SIZE_INFORMATION的structure的参数,需DACL的信息,是为了
// 方便我们遍历其中的ACE。
AclInfo.AceCount = 0; // Assume NULL DACL.
AclInfo.AclBytesFree = 0;
AclInfo.AclBytesInUse = sizeof(ACL);
if (pACL == NULL)
fDaclPresent = FALSE;
// 如果DACL不为空,则取其信息。(大多数情况下“自关联”的DACL为空)
if (fDaclPresent) {
if (!GetAclInformation(pACL, &AclInfo,
sizeof(ACL_SIZE_INFORMATION), AclSizeInformation)) {
_tprintf(TEXT("GetAclInformation() failed. Error %d\n"),
GetLastError());
__leave;
}
}
//
// STEP 5 如果文件(目录) DACL 有数据,将指定帐户的ACE的访问权限转换到整型数组
//
// 下面的代码假设首先检查指定文件(目录)是否存在的DACL,如果有的话,
// 那么就将指定帐户的ACE的访问权限转换到整型数组,我们可以看到其遍历的方法
// 是采用ACL_SIZE_INFORMATION结构中的AceCount成员来完成的。在这个循环中,
// 查找和指定账户相关的ACE
//
if (fDaclPresent && AclInfo.AceCount) {
for (CurrentAceIndex = 0;
CurrentAceIndex < AclInfo.AceCount;
CurrentAceIndex++) {
//
// STEP 10: 从DACL中取ACE
//
if (!GetAce(pACL, CurrentAceIndex, &pTempAce)) {
_tprintf(TEXT("GetAce() failed. Error %d\n"),
GetLastError());
__leave;
}
//
//
// STEP 6: 检查要拷贝的ACE的SID是否和需要加入的ACE的SID一样,
// 如果一样,那么就将该ACE的访问权限转换到整型数组,
// 否则跳过,进行下一个循环
//
int nAceType = 1;
if (EqualSid(pUserSID,
&(((ACCESS_ALLOWED_ACE *)pTempAce)->SidStart)))
{
if(((PACE_HEADER)pTempAce)->AceType == ACCESS_DENIED_ACE_TYPE)
{
nAceType = 2;
}
else
{
nAceType = 1;
}
//bitset类代表的整型数值的顺序是从0到N-1
bitset<32> bitAccessMask(((ACCESS_ALLOWED_ACE*)pTempAce)->Mask);
for (int i = 0; i < 32; i++)
{
if (bitAccessMask[i] != 0 && arrRights[i] != 2)
{
arrRights[i] = nAceType;
}
}
}
else
{
continue;
}
}
}
fResult = TRUE;
} __finally {
//
// STEP 7: 释放已分配的内存,以免Memory Leak
//
if (pUserSID) myheapfree(pUserSID);
if (szDomain) myheapfree(szDomain);
if (pFileSD) myheapfree(pFileSD);
}
return fResult;
}
int _tmain(int argc, TCHAR *argv[])
{
if (argc < 3) {
_tprintf(TEXT("usage: \"%s\" <FileName> <AccountName>\n"), argv[0]);
return 1;
}
//关于ACCESS_MASK中各个位代表的含义请参考MSDN
string filedesc[] = {"FILE_READ_DATA", "FILE_WRITE_DATA", "FILE_APPEND_DATA", "FILE_READ_EA",
"FILE_WRITE_EA", "FILE_EXECUTE", "FILE_DELETE_CHILD", "FILE_READ_ATTRIBUTES",
"FILE_WRITE_ATTRIBUTES", " ", " ", " ",
" ", " ", " ", " ",
"DELETE ", "READ_CONTROL", "WRITE_DAC", "WRITE_OWNER",
"SYNCHRONIZE ", " ", " "," ",
"ACCESS_SYSTEM_SECURITY", "MAXIMUM_ALLOWED", " "," ",
"GENERIC_ALL", "GENERIC_EXECUTE", "GENERIC_WRITE","GENERIC_READ"};
string rights[] = {"Allow", "Deny"};
//获取ACE中的访问权限
//ACE中的访问权限是通过DWORD类型的ACCESS_MASK记录的
//GetAccountRights函数将ACCESS_MASK转为了一个32个元素的整型数组,并传出
int arrRights[32] = {0};
if (!GetAccountRights(argv[1], argv[2], arrRights))
{
_tprintf(TEXT("GetAccountRights() failed.\n"));
}
else
{
_tprintf(TEXT("The access rights of the file is..\n"));
for (int i = 0; i < 32; i++)
{
int nTmpRight = arrRights[i];
if (nTmpRight != 0)
{
cout<<filedesc[i]<<": "<<rights[nTmpRight-1]<<endl;
}
}
}
// argv[1] – 文件(目录)名
// argv[2] – 用户(组)名
// GENERIC_ALL表示所有的权限,其是一系列的NTFS权限的或
// NTFS的文件权限很细,还请参看MSDN。
if (!AddAccessRights(argv[1], argv[2], GENERIC_ALL)) {
_tprintf(TEXT("AddAccessRights() failed.\n"));
return 1;
}
else {
_tprintf(TEXT("AddAccessRights() succeeded.\n"));
return 0;
}
}
函数AddAccessRights实现为文件(目录)添加一个帐户(组)的权限
函数GetAccountRights实现获取该文件(目录)指定帐户(组)的权限
三、 一些相关的API函数
通过以上的示例,相信你已知道如何操作NTFS文件安全属性了,还有一些API函数需要介绍一下。
1、 如果你要加入一个Access-Denied 的ACE,你可以使用AddAccessDeniedAce函数
2、 如果你要删除一个ACE,你可以使用DeleteAce函数
3、 如果你要检查你所设置的ACL是否合法,你可以使用IsValidAcl函数,同样,对于SD的合法也有一个叫IsValidSecurityDescriptor的函数
4、 MakeAbsoluteSD和MakeSelfRelativeSD两个函数可以在两种SD的格式中进行转换。
5、 使用SetSecurityDescriptorDacl 和 SetSecurityDescriptorSacl可以方便地把ACL设置到SD中。
6、 使用GetSecurityDescriptorDacl or GetSecurityDescriptorSacl可以方便地取得SD中的ACL结构。
我们把一干和SD/ACL/ACE相关的API函数叫作Low-Level Security Descriptor Functions,其详细信息还请参看MSDN。