Windows Access Control(访问控制)知识点及 C# 代码段

前言

写这篇是想详细总结下关于 Windows 的安全对象(文件或目录等)权限相关的知识以及如何用 C# 代码进行设置,因为在编码时遇到了些问题。有问题并解决了,总是要做个总结报告的,同时顺便整理下收藏夹。

Access Control 必备知识

访问控制总述

Windows 访问控制由访问控制模型实现。访问控制模型使你能够控制 进程 访问 安全对象 或执行各种系统管理任务的能力。
访问控制模型有两个基本部分:

  • 访问令牌,其中包含有关已登录用户的信息
  • 安全描述符,其中包含保护安全对象的安全信息

当用户登录时,系统会对用户的帐户名和密码 进行身份验证 。 如果登录成功,系统会创
建访问令牌。 代表此用户执行的每个 进程 都将具有此访问令牌的副本。 访问令牌包含
用于标识用户帐户和用户所属的任何组帐户 的安全标识符 。 令牌还包含用户或 用户组
拥有的权限
列表。 当进程尝试访问安全对象或执行需要特权的系统管理任务时,系统将
使用此令牌来标识关联的用户。
创建安全对象时,系统会为其分配一个包含其创建者指定的安全信息的安全描述符 ;如果未指定安全信息,则为其分配默认安全信息。应用程序可以使用函数来检索和设置现有对象的安全信息。

安全描述符标识对象的所有者,还可以包含以下 访问控制列表

  • 可自由访问的控制列表 (DACL) ,用于标识允许或拒绝访问对象的用户和组
  • 系统访问控制列表 (SACL) ,用于控制系统审核尝试访问对象的方式

ACL 包含访问控制项(ACE) ** 的列表 。 每个 ACE 指定一组 访问权限 ,并包含一个 SID
用于标识允许、拒绝或审核其权限的 受托人受信人(受托人)可以是用户帐户、组帐户或 登录
会话。
使用函数操作
安全描述符SID** 和 ACL 的内容,而不是直接访问它们。
下面就对各个术语做详细说明(会比较枯燥,要用就得理解清楚,没办法)。

Access Token(访问令牌)

访问令牌是描述进程或线程的安全上下文的对象。 令牌中的信息包括与进程或线程关联的用户帐户的标识和权限。 当用户登录时,系统会通过将用户密码与存储在安全数据库中(如域认证中的NTDS或本地认证中的SAM文件)的信息进行比较来验证用户的密码。 如果密码经过身份验证,系统会生成访问令牌(通常我们在输入密码登陆进入Windows界面时就是一个生成访问令牌的过程)。 代表此用户执行的每个进程都有此访问令牌的副本。

当线程与安全对象交互或尝试执行需要特权的系统任务时,系统使用访问令牌来标识用户。 访问令牌包含以下信息:

  • 用户帐户的安全标识符 (SID)
  • 用户所属组的 SID
  • 标识当前登录会话的登录SID
  • 用户或用户组拥有 的权限 的列表
  • 所有者 SID
  • 主要组的 SID
  • 用户在未指定安全描述符的情况下创建安全对象时系统使用的默认 DACL
  • 访问令牌的源
  • 令牌是主令牌还是模拟令牌
  • 限制 SID 的可选列表
  • 当前模拟级别
  • 其他统计信息

access_token_elements

图 1 Access Token 所包含的信息

每个进程都有一个 主令牌 ,用于描述与进程关联的用户帐户 的安全上下文 。 默认情况下,当进程的线程与安全对象交互时,系统会使用主令牌。此外,线程可以模拟客户端帐户。 模拟允许线程使用客户端的安全上下文与安全对象交互。 模拟客户端的线程同时具有主令牌和 模拟令牌。

官网介绍访问令牌及各Win2Api
网友的博客:访问令牌

SD(Security Descriptors, 安全描述符)

**安全描述符包含与安全对象关联的安全信息。 **
安全描述符由 SECURITY_DESCRIPTOR 结构及其关联的安全信息组成。 安全描述符可以包含以下安全信息:

  • 安全对象的 所有者和主组的安全标识符 (SID) (访问令牌和安全描述符都有SID项)。
  • 一个 DACL(下面讲到) ,指定允许或拒绝的特定用户或组的访问权限。
  • 一个 SACL(下面讲到),指定为对象生成审核记录的访问尝试的类型。
  • 一组控制位,用于限定安全描述符或其单个成员的含义。

应用程序不得直接操作安全描述符的内容。 Windows API 提供用于在对象的安全描述符中设置和检索安全信息的函数。 此外,还有用于为新对象创建和初始化安全描述符的函数。

在 Active Directory 对象上使用安全描述符的应用程序可以使用 Windows 安全功能或 Active Directory 服务接口 (ADSI) 提供的安全接口(这个是在域中的应用)。

security_descriptor

图 2 安全描述符

官网介绍安全描述符

安全对象

安全对象是可以具有 安全描述符的对象。所有命名的 Windows 对象都是安全的。 某些未命名的对象(如 进程 对象和线程对象)也可以具有安全描述符。 对于大多数安全对象,可以在创建对象的函数调用中指定对象的 安全描述符 。 例如,可以在 CreateFile 和 CreateProcess 函数中指定安全描述符。

此外,Windows 安全功能使你能够获取和设置在 Windows 以外的操作系统上创建的安全对象的安全信息。 Windows 安全功能还支持将安全描述符与应用程序定义的专用对象配合使用。 有关专用安全对象的详细信息,请参阅客户端/服务器访问控制。

每种类型的安全对象定义其自己的一组特定 访问权限 和自己的 泛型访问权限映射。
安全对象是可以具有安全描述符的对象。比如文件、目录、管道、进程、注册表等.
图2 的红框就是安全对象:目录对象。

常见安全对象:

  • NTFS 文件系统上的文件或目录
  • 命名管道和匿名管道
  • 进程和线程
  • 文件映射对象
  • 访问令牌
  • 窗口管理对象 (窗口工作站 和 桌面)
  • 注册密钥
  • Windows 服务
  • 本地或远程打印机
  • 网络共享
  • 进程间同步对象 (事件、互斥体、信号灯和可等待计时器)
  • 作业对象
  • 目录服务对象

官网介绍安全对象

ACL(Access Control List,访问控制列表)

访问控制列表 (ACL) 是访问控制条目 (ACE) 的列表。
ACL中的每个ACE标识一个受信者,并指定该受信者允许、拒绝或审核的访问权限。
安全对象的安全描述符可以包含两种类型的 ACL:DACL 和 SACL。(看下面的图4、图5.

DACL:Discretionary Access Control List,自由访问控制列表;

SACL:System Access Control List,系统访问控制列表。

access_control_list

图 3 访问控制列表(由一个个A CE 组成,DACL 由 DACE 组成,SACL由 SACE 组成)

官网介绍访问控制列表

ACE(Access Control Entry, 访问控制条目)

ACE (访问控制项) 是 ACL (访问控制列表)中的一个元素。
ACL 可以有零个或多个 ACE。
每个 ACE 控制或监视指定受信人对对象的访问。

有 6 种类型的 ACE,其中 3 种受所有安全对象支持。 其他 3 种类型是目录服务对象支持的 特定于对象的 ACE。

所有类型的 ACE 包含以下访问控制信息:

  • 一个 安全标识符 (SID) ,用于标识 ACE 所应用到的受托人
  • 一个 访问掩码 ,指定 ACE 控制 的访问权限 。
  • 指示 ACE 类型的标志。
  • 一组位标志,用于确定子容器或对象是否可以从 ACL 附加到的主对象继承 ACE。

dacl_ace

图 4 DACL 和 DACL 的 ACE

sacl_ace

图 5 SACL 和 SACL 的 ACE

官网介绍访问控制条目

受托人(受信人)

受托人是访问控制项 (ACE) 适用的用户帐户、组帐户或登录会话。 访问控制 列表中的 每个 ACE (ACL) 都有一个 安全标识符 (SID) ,用于标识受托人。

用户帐户包括用户或程序(如 Windows 服务)用于登录到本地计算机的帐户。

组帐户不能用于登录到计算机,但它们在 ACE 中可用于允许或拒绝对一个或多个用户帐户的一组访问权限。

标识当前登录会话的登录 SID 仅在用户注销之前才允许或拒绝访问权限。

访问控制函数使用 TRUSTEE 结构来标识受托人。 利用 TRUSTEE 结构,可以使用名称字符串或 SID 来标识受托人。 如果使用名称,则从 TRUSTEE 结构创建 ACE 的函数执行分配 SID 缓冲区和查找与帐户名称对应的 SID 的任务。

有两个帮助程序函数 BuildTrusteeWithSidBuildTrusteeWithName ,它们使用指定的 SID 或名称初始化 TRUST 结构。 BuildTrusteeWithObjectsAndSidBuildTrusteeWithObjectsAndName 允许您使用特定于对象的 ACE 信息初始化 TRUST 结构。 另外三个帮助程序函数 GetTrusteeFormGetTrusteeNameGetTrusteeType 检索 TRUSTEE 结构的各个成员的值。

官网介绍受托人

权限控制 C# 基本代码

C# 代码微软官网上有,在权限更改后,可以看看文件或文件夹属性->安全,与没改权限之前进行对比,会更具体,对上述的各术语理解印象会更深。

using System;
using System.IO;
using System.Security.AccessControl;

...

// Adds an ACL entry on the specified file for the specified account.
public static void AddFileSecurity(string fileName, string account,
    FileSystemRights rights, AccessControlType controlType)
{

    // Get a FileSecurity object that represents the
    // current security settings.
    FileSecurity fSecurity = File.GetAccessControl(fileName);

    // Add the FileSystemAccessRule to the security settings.
    fSecurity.AddAccessRule(new FileSystemAccessRule(account,
        rights, controlType));

    // Set the new access settings.
    File.SetAccessControl(fileName, fSecurity);
}

// Removes an ACL entry on the specified file for the specified account.
public static void RemoveFileSecurity(string fileName, string account,
    FileSystemRights rights, AccessControlType controlType)
{

    // Get a FileSecurity object that represents the
    // current security settings.
    FileSecurity fSecurity = File.GetAccessControl(fileName);

    // Remove the FileSystemAccessRule from the security settings.
    fSecurity.RemoveAccessRule(new FileSystemAccessRule(account,
        rights, controlType));

    // Set the new access settings.
    File.SetAccessControl(fileName, fSecurity);
}

下面是对上面两个函数的调用:

// Add the access control entry to the file.
AddFileSecurity(fileName, @"DomainName\AccountName",FileSystemRights.ReadData,AccessControlType.Allow);


// Remove the access control entry from the file.
RemoveFileSecurity(fileName, @"DomainName\AccountName",FileSystemRights.ReadData, AccessControlType.Allow);

微软示例代码

C# 代码进行权限控制

  • C# 如何设置系统文件夹的 Owner

    在用 C# 代码进行权限控制时,由于运行程序的访问令牌(包含的用户)的权限不够,对有些文件夹,尤其由 TrustedInstaller 账号所属的文件夹(系统文件夹)进行权限设置时,会抛出 UnAuthorized Exception.
    为了解决这个问题,代码这样写:

      ```
      [DllImport("ntdll.dll", SetLastError = true)]
      static extern IntPtr RtlAdjustPrivilege(int Privilege, bool bEnablePrivilege, bool IsThreadPrivilege, out bool PreviousValue);
    
      [DllImport("advapi32.dll")]
      static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref UInt64 lpLuid);
    
      [DllImport("advapi32.dll")]
      static extern bool LookupPrivilegeValue(IntPtr lpSystemName, string lpName, ref UInt64 lpLuid);
    
      ulong luid = 0;
      bool throwaway;
      //开启权限,
      LookupPrivilegeValue(IntPtr.Zero, "SeRestorePrivilege", ref luid);
      RtlAdjustPrivilege((int)luid, true, false, out throwaway);
      LookupPrivilegeValue(IntPtr.Zero, "SeBackupPrivilege", ref luid);
      RtlAdjustPrivilege((int)luid, true, false, out throwaway);
      LookupPrivilegeValue(IntPtr.Zero, "SeTakeOwnershipPrivilege", ref luid);
                  RtlAdjustPrivilege((int)luid, true, false, out throwaway);
    
      //dir = "C:\\Windows"
      DirectoryInfo dInfo = new DirectoryInfo(dir);
      DirectorySecurity dirSec = dInfo.GetAccessControl(AccessControlSections.All);
      //get windows login user
      WindowsIdentity windowsIdentity = WindowsIdentity.GetCurrent();
      SecurityIdentifier sid = windowsIdentity.User;
    
      //first set owner and SetAccessControl, then add access rule and SetAccessControl in order. 
      //if not, the program will throw "UnAuthorized Operation"
      //only access control been set, then the new owner can be effective.
      dirSec.SetOwner(sid);
      Directory.SetAccessControl(dir, dirSec);
    
      //用完之后需要关闭权限
      ulong luid = 0;
      bool throwaway;
      LookupPrivilegeValue(IntPtr.Zero, "SeTakeOwnershipPrivilege", ref luid);
      RtlAdjustPrivilege((int)luid, false, false, out throwaway);
    
      LookupPrivilegeValue(IntPtr.Zero, "SeRestorePrivilege", ref luid);
      RtlAdjustPrivilege((int)luid, false, false, out throwaway);
      LookupPrivilegeValue(IntPtr.Zero, "SeBackupPrivilege", ref luid);
      RtlAdjustPrivilege((int)luid, false, false, out throwaway);
      ```
    

    编程方式更改权限的步骤(必须要这样做):

    • Run with Administrator rights.

    • Take ownership of the object (registry key/directory/whatever).

    • Allow yourself r/w access by changing the DACL.

    • Now make your changes.

    • Set the owner back to trusted installer and undo the permission changes you've made.

  • C# 如何确定 AccessRule 是从哪个对象继承的*

    目前知道的只能使用递归:

      ```
      void PrintAccessRules(string path)
      {   
          var security = File.GetAccessControl(path);
    
          var accessRules = security.GetAccessRules(true, true, typeof(NTAccount));
    
    
          foreach (var rule in accessRules.Cast<FileSystemAccessRule>())
          {
              if (!rule.IsInherited)
              {
                  Console.WriteLine("{0} {1} to {2} was set on {3}.", rule.AccessControlType, rule.FileSystemRights, rule.IdentityReference, path);
                  continue;
              }
    
              FindInheritedFrom(rule, Directory.GetParent(path).FullName);
          }
      }
    
      void FindInheritedFrom(FileSystemAccessRule rule, string path)
      {
          var security = File.GetAccessControl(path);
          var accessRules = security.GetAccessRules(true, true, typeof(NTAccount));
    
          var matching = accessRules.OfType<FileSystemAccessRule>()
              .FirstOrDefault(r => r.AccessControlType == rule.AccessControlType && r.FileSystemRights == rule.FileSystemRights && r.IdentityReference == rule.IdentityReference);
    
          if (matching != null)
          {
              if (matching.IsInherited) FindInheritedFrom(rule, Directory.GetParent(path).FullName);
              else Console.WriteLine("{0} {1} to {2} is inherited from {3}", rule.AccessControlType, rule.FileSystemRights, rule.IdentityReference, path);
          }
      }
      ```
    

    Determine where FileSystemAccessRule is inherited from in C#

  • C# 设置文件夹权限,只显示特殊权限的问题

    设置权限时要把应用到子文件及子文件夹 flag 设上:

      ```
      //first set owner and SetAccessControl, then add access rule and SetAccessControl in order. 
      //if not, the program will throw "UnAuthorized Operation"
      //only access control been set, then the new owner can be effective.
      dirSec.SetOwner(sid);
      Directory.SetAccessControl(dir, dirSec);
    
      //need flags:InheritanceFlags.ContainerInherit|InheritanceFlags.ObjectInherit, or sid only has 特殊权限。
      //sid对该文件夹和该文件夹内的文件权限不一致导致,因为权限不一致,所以只对该文件夹有设定的权限。导致不能写文件数据(throw Unauthorised)。
      //增加的flags表示对该文件夹和该文件夹的子文件夹和文件都应用这个access rule.
      //C:\Windows\schemas\TSWorkSpace,这个文件夹有审核设置。
      //其他文件夹没碰到过这个问题
      FileSystemAccessRule fsar = new FileSystemAccessRule(sid, FileSystemRights.Write, 
          InheritanceFlags.ContainerInherit|InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow);
      dirSec.AddAccessRule(fsar);
      Directory.SetAccessControl(dir, dirSec);
      ```
    
  • C# 如何删除或修改继承过来的权限*

    先把继承过来的权限去除掉,然后才可以去除指定账号的所有权限,之后才能设置自定义的权限。

    it is called SetAccessRuleProtection(bool isProtected, bool PreserveInheritience)

    the first boolean sets whether to inherit parent objects (false to inherit, true to notinherit)
    the second tells whether to copy the variables to the ACL (true to copy, false to not copy)

    so to turn off inheritence and copy the inherited permissions to the object call the function with both arguments as true. As soon as inheritence is turned off you can remove a user from the acl using the DirectorySecurity.PurgeAccessRules(IdentityReference sid) commmand.

      ```
      static void CreateMyPublicWeb(string path, string username)
      {
          
          string user = "mypublicweb";
          SecurityIdentifier sid = getSidByUserName(user);
          SecurityIdentifier eRaiderSid = getSidByUserName(username);
    
          if (!Directory.Exists(path + "\\mypublicweb"))
          {
              DirectoryInfo di = Directory.CreateDirectory(path + "\\mypublicweb");
              DirectorySecurity dSec = di.GetAccessControl();
              //below command turns off inheritence and copies inherited aces to the object.
              dSec.SetAccessRuleProtection(true, true);
              di.SetAccessControl(dSec);
          }
          else
          {
              DirectoryInfo di = new DirectoryInfo(path + "\\mypublicweb");
              DirectorySecurity dSec = di.GetAccessControl();
              dSec.SetAccessRuleProtection(true, true);
              di.SetAccessControl(dSec);
          }
          
          DirectoryInfo dinfo = new DirectoryInfo(path + "\\mypublicweb");
          DirectorySecurity dSecurity = dinfo.GetAccessControl();
    
          //this loop looks for permissions i want to reset/create.
          foreach (FileSystemAccessRule ace in dSecurity.GetAccessRules(true,true,typeof(SecurityIdentifier)))
          {
              if (ace.IdentityReference == eRaiderSid || ace.IdentityReference == sid)
              {
                  //if it finds an ace with the sid i want to create/reset i remove all aces for that user.
                  dSecurity.PurgeAccessRules(ace.IdentityReference);
                  dinfo.SetAccessControl(dSecurity);
              }
          }
          //create/reset the permissions. 
          dSecurity.AddAccessRule(new FileSystemAccessRule(sid, AccessMask_IUSR_ThisFolderSubFolderAndFiles, IFlag_ThisFolderSubfoldersAndFiles, PFlag_ThisFolderSubfoldersAndFiles, AccessControlType.Allow));
          dSecurity.AddAccessRule(new FileSystemAccessRule(eRaiderSid, AccessMask_User_ThisFolderOnly, IFlag_ThisFolderOnly, PFlag_ThisFolderOnly, AccessControlType.Allow));
          dSecurity.AddAccessRule(new FileSystemAccessRule(eRaiderSid, AccessMask_User_SubfoldersOnly, IFlag_SubfoldersOnly, PFlag_SubfoldersOnly, AccessControlType.Allow));
          dSecurity.AddAccessRule(new FileSystemAccessRule(eRaiderSid, AccessMask_User_FilesOnly, IFlag_FilesOnly, PFlag_FilesOnly, AccessControlType.Allow));
          dinfo.SetAccessControl(dSecurity);
      ```
    

    How to remove/modify an inherited permission.

小结

小结都不知道怎么写了,感觉这篇有点杂,但知识点还是在的。希望对读者有用。

posted @ 2023-06-19 18:54  ALLEN_2008  阅读(363)  评论(0编辑  收藏  举报