1. 从一次权限排查说起:为什么你需要搞懂Windows安全机制的底层逻辑
前阵子帮一个朋友排查他内部工具的问题,场景很典型:一个用C#写的后台服务,以LocalSystem身份跑着,需要往某个目录写日志文件,结果死活写不进去,报Access Denied。他反复确认了目录权限,Everyone给了完全控制,还是不行。最后我让他把进程的令牌信息打出来一看,问题瞬间明朗——那个服务进程的令牌里,Everyone这个SID压根就没出现在访问令牌的组列表里,因为服务账户的令牌经过了过滤,某些高权限组被标记为"仅用于拒绝"(Deny-only)。这就是典型的"你以为你有权限,其实你的令牌里根本没有这个身份"的情况。
类似这种问题,在Windows平台上做开发、运维、安全加固的人几乎都会遇到。你可能调过icacls、用过takeown、在代码里写过File.SetAccessControl,甚至直接改过注册表的ACL。但很多人对这些操作背后的机制其实是一知半解的——知道怎么改,但不知道为什么这么改,出了问题也不知道从哪儿查。
这篇文章就是想把Windows安全机制里最核心的三块拼图讲透:访问令牌(Access Token)、安全描述符(Security Descriptor)、访问检查(Access Check)。这三个东西构成了Windows整个权限体系的骨架。你理解了它们之间的关系,基本上90%的权限问题你都能自己定位和解决。不管你是做Windows客户端开发、服务端运维、还是搞安全研究,这套知识都是绕不过去的基本功。
我尽量不写成教科书,而是按照一个从业者的视角,把这三个概念拆开揉碎,配上实操验证的方法,让你看完能直接上手排查问题。
2. 访问令牌:进程的"身份证"到底长什么样
2.1 令牌的本质:不只是用户名密码那么简单
很多人第一次接触"令牌"这个词,会把它和Web开发里的Token搞混。Windows的访问令牌完全是另一回事。你可以把它理解成进程或线程的身份证+工作证的组合——它不光证明"你是谁",还规定了"你能干什么"。
当一个用户登录Windows时,系统会验证凭据(密码、PIN、生物识别等),验证通过后就会创建一个主令牌(Primary Token)。这个令牌里包含了:
- 用户SID:这个账户的唯一安全标识符,比如
S-1-5-21-xxx-xxx-xxx-1001 - 组SID列表:该用户所属的所有组的SID
- 特权列表:该账户拥有的系统特权,比如
SeDebugPrivilege、SeBackupPrivilege - 完整性级别(Integrity Level):从Low到System,决定能访问什么级别的资源
- 登录会话ID:标识这次登录会话
- 令牌类型:主令牌还是模拟令牌
每次你启动一个进程,这个进程就会拿到一份主令牌的副本。之后这个进程访问任何受保护资源时,系统都会拿这个令牌去和资源的安全描述符做比对。
这里有个关键点很多人不知道:令牌里的组列表并不是你所属的所有组都会生效。有些组会被标记为SE_GROUP_USE_FOR_DENY_ONLY,意思是这个组身份只能用来拒绝访问,不能用来允许访问。UAC开启后,管理员账户的令牌就会经过这种过滤——管理员组被标记为Deny-only,所以即使用户是管理员,普通进程也不具备管理员权限,直到你"以管理员身份运行"。
2.2 用whoami和Process Explorer把令牌看个透
光说概念没意思,咱们直接看实物。Windows自带的whoami命令就能看到当前令牌的不少信息:
whoami /all这条命令会输出当前用户的SID、组列表、特权列表、完整性级别。输出大概长这样:
USER INFORMATION ---------------- User Name SID ================ ============================================= desktop\testuser S-1-5-21-1234567890-1234567890-1234567890-1001 GROUP INFORMATION ----------------- Group Name Type SID Attributes ====================================== ================ ============ ================================================== Everyone Well-known group S-1-1-0 Mandatory group, Enabled by default, Enabled group BUILTIN\Users Alias S-1-5-32-545 Mandatory group, Enabled by default, Enabled group ... PRIVILEGES INFORMATION ---------------------- Privilege Name Description State ============================= ==================================== ======== SeShutdownPrivilege Shut down the system Disabled SeChangeNotifyPrivilege Bypass traverse checking Enabled ...注意看Attributes那一列,如果某个组显示Use for deny only,那就说明这个组在当前令牌里只能用来拒绝访问。这就是UAC过滤后的典型表现。
如果你想看任意进程的令牌,那就得用Process Explorer(Sysinternals套件里的)。打开Process Explorer,双击任意进程,切到Security标签页,就能看到该进程令牌的完整信息,包括用户、组、特权。这个工具在排查"为什么这个进程访问不了那个资源"的时候特别好使。
还有一个更底层的工具是Sysinternals的accesschk,命令行操作,适合脚本化排查:
accesschk.exe -p <PID>2.3 模拟令牌:线程级别的身份切换
除了主令牌,Windows还有模拟令牌(Impersonation Token)。这个东西的存在是因为服务端程序经常需要"以调用者的身份"去访问资源。
举个例子:你写了一个Web服务,运行在IIS的应用池账户下。当一个用户请求进来时,你可能希望以这个用户的身份去读取他有权访问的文件,而不是用应用池账户的身份。这时候就需要模拟——线程临时切换到调用者的令牌上去执行操作。
模拟有四个级别,从低到高:
| 级别 | 名称 | 能做什么 |
|---|---|---|
| SecurityAnonymous | 匿名 | 不能获取调用者身份 |
| SecurityIdentification | 标识 | 能获取调用者身份,但不能模拟 |
| SecurityImpersonation | 模拟 | 能在本地模拟调用者 |
| SecurityDelegation | 委托 | 能在远程模拟调用者 |
大部分场景用SecurityImpersonation就够了。SecurityDelegation需要域环境支持Kerberos约束委派或非约束委派,配置起来更复杂。
在代码里,模拟通常通过LogonUser+ImpersonateLoggedOnUser或者WindowsIdentity.Impersonate(.NET)来实现。这里有个坑:模拟令牌用完必须还原,否则线程会一直以模拟身份运行,后续操作可能出问题。.NET里用using块包住WindowsIdentity.Impersonate的返回值就能自动还原。
实操心得:排查服务程序的权限问题时,第一件事就是确认当前线程到底在用哪个令牌。很多"莫名其妙"的权限问题,根源就是模拟没有正确还原,或者模拟级别不够。
3. 安全描述符:每个对象的"门禁规则"
3.1 安全描述符的四个组成部分
如果说令牌是"你是谁",那安全描述符就是"谁能进这个门"。Windows里每一个安全对象(Securable Object)——文件、注册表键、进程、线程、事件、互斥体、命名管道等等——都挂着一个安全描述符。
安全描述符的结构用SECURITY_DESCRIPTOR表示,包含四个核心部分:
- Owner SID:对象的所有者,所有者永远有权限修改对象的DACL
- Group SID:主要用于POSIX兼容,Windows里基本不用
- DACL(Discretionary Access Control List):自主访问控制列表,决定谁有什么权限
- SACL(System Access Control List):系统访问控制列表,决定哪些操作会被审计
DACL是日常打交道最多的。它由一系列**ACE(Access Control Entry)**组成,每个ACE包含:
- ACE类型:允许(Allow)、拒绝(Deny)、审计(Audit)
- 受托者SID:这个ACE针对哪个用户或组
- 访问掩码:具体是什么权限,比如读、写、执行、删除
- 继承标志:这个ACE是否会被子对象继承
3.2 Allow和Deny的优先级:为什么"拒绝"永远赢
这是访问检查里最重要的一条规则:Deny ACE永远优先于Allow ACE。
系统在做访问检查时,会遍历DACL里的每一个ACE。如果遇到一个Deny ACE匹配了请求的权限,直接拒绝,不管后面有没有Allow ACE。只有当所有Deny ACE都不匹配时,才会去看Allow ACE是否覆盖了请求的权限。
这个规则的实际影响很大。比如你给某个目录设置了Everyone: 完全控制,同时又设置了UserA: 拒绝写入,那UserA就是写不了。即使Everyone允许了也没用,因为Deny优先。
很多人在排查权限问题时忽略了Deny ACE的存在。用icacls查看权限时,如果看到(DENY)标记的条目,就要特别留意:
icacls C:\SomeFolder输出里如果有类似这样的行:
C:\SomeFolder DESKTOP\UserA:(DENY)(W) Everyone:(F)那就说明UserA被显式拒绝了写入权限。
3.3 继承机制:为什么子目录的权限会"莫名其妙"变化
安全描述符里的ACE可以标记为可继承,意思是这个ACE会自动传播到子对象。继承分两种:
- 容器继承(Container Inherit, CI):子目录会继承
- 对象继承(Object Inherit, OI):子文件会继承
还有一个**仅继承(Inherit Only, IO)**标志,表示这个ACE不作用于当前对象,只传给子对象。
继承机制在实际使用中经常出问题。比如你在父目录改了一个ACE,子目录的权限可能跟着变;或者你复制了一个文件到另一个目录,它的权限可能保留原来的,也可能继承新位置的,取决于你是复制还是移动。
用icacls可以看到继承标志:
C:\Parent BUILTIN\Users:(OI)(CI)(RX) BUILTIN\Administrators:(OI)(CI)(F)(OI)(CI)就表示这个ACE会同时被文件和子目录继承。
注意事项:修改权限时,如果只想影响当前对象而不影响子对象,一定要把继承标志去掉。反过来,如果希望子对象跟随父对象,就要确保ACE带有正确的继承标志。用
icacls的/inheritance参数可以控制继承行为。
3.4 用PowerShell和icacls实操安全描述符
理论说了一堆,咱们直接上手看。PowerShell里查看文件的安全描述符:
$acl = Get-Acl -Path "C:\TestFolder" $acl | Format-List $acl.Access | Format-Table IdentityReference, FileSystemRights, AccessControlType, IsInheritedGet-Acl返回的是一个.NET的FileSecurity对象,它封装了安全描述符。Access属性就是DACL里的ACE列表。
如果要修改权限,可以这样:
$acl = Get-Acl -Path "C:\TestFolder" $rule = New-Object System.Security.AccessControl.FileSystemAccessRule( "BUILTIN\Users", # 受托者 "Read, Execute", # 权限 "ContainerInherit, ObjectInherit", # 继承标志 "None", # 传播标志 "Allow" # 允许还是拒绝 ) $acl.AddAccessRule($rule) Set-Acl -Path "C:\TestFolder" -AclObject $acl用icacls做同样的事:
icacls C:\TestFolder /grant "BUILTIN\Users:(OI)(CI)(RX)"icacls的权限缩写对照:
| 缩写 | 含义 |
|---|---|
| F | 完全控制 |
| M | 修改 |
| RX | 读取和执行 |
| R | 只读 |
| W | 只写 |
| D | 删除 |
这些缩写可以组合,比如(OI)(CI)(M)表示子对象继承的修改权限。
3.5 SACL:审计策略的落地方式
SACL平时用得少,但在安全合规场景下很重要。它定义了哪些访问操作会被记录到安全事件日志里。
配置SACL需要SeSecurityPrivilege特权,普通管理员默认没有这个特权,需要用secpol.msc或者组策略来分配。
用auditpol命令可以查看和设置审计策略:
auditpol /get /category:* auditpol /set /subcategory:"File System" /success:enable /failure:enable设置好审计策略后,再给具体对象的SACL添加审计ACE,就能记录对该对象的访问了。审计日志在事件查看器的"Windows日志 -> 安全"里查看,事件ID通常是4656(请求对象句柄)、4663(访问对象)等。
4. 访问检查:令牌和安全描述符的"对决"
4.1 访问检查的完整流程
当一个线程试图打开一个对象(比如用CreateFile打开文件)时,内核里的**安全引用监视器(Security Reference Monitor)**会执行访问检查。流程大致如下:
- 获取线程的有效令牌:如果线程正在模拟,用模拟令牌;否则用进程的主令牌
- 获取对象的DACL:从对象的安全描述符里取出DACL
- 遍历DACL:按顺序检查每个ACE
- 匹配SID:看ACE里的受托者SID是否在令牌的用户SID或组SID列表里
- 检查ACE类型:如果是Deny ACE且匹配了请求的权限,直接拒绝
- 累积Allow权限:如果是Allow ACE,把权限累积起来
- 最终判断:如果累积的Allow权限覆盖了请求的所有权限,允许访问;否则拒绝
这个过程有个细节:令牌里的组SID如果标记为SE_GROUP_USE_FOR_DENY_ONLY,只能匹配Deny ACE,不能匹配Allow ACE。这就是前面说的UAC过滤的效果。
4.2 通用访问权限和特定访问权限
访问掩码里的权限分两类:
通用权限(Generic Rights):
GENERIC_READGENERIC_WRITEGENERIC_EXECUTEGENERIC_ALL
标准权限(Standard Rights):
DELETEREAD_CONTROL(读安全描述符)WRITE_DAC(改DACL)WRITE_OWNER(改所有者)SYNCHRONIZE(等待对象)
特定权限(Specific Rights):每种对象类型不一样。文件有FILE_READ_DATA、FILE_WRITE_DATA等;注册表键有KEY_READ、KEY_WRITE等。
通用权限在访问检查时会被映射成具体对象类型的特定权限。比如GENERIC_READ对文件来说会映射成FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | STANDARD_RIGHTS_READ | SYNCHRONIZE。
这个映射关系在排查问题时很有用。有时候你看到某个进程请求的是GENERIC_WRITE,但DACL里只给了FILE_WRITE_DATA,看起来好像够了,实际上GENERIC_WRITE还包含其他权限位,可能就不够了。
4.3 用accesschk验证访问检查结果
accesschk是验证访问检查结果的神器。比如你想知道当前用户对某个目录有什么权限:
accesschk.exe -d C:\TestFolder输出会列出当前用户对该目录的有效权限。
想看某个特定用户:
accesschk.exe -d -u DESKTOP\UserA C:\TestFolder想看进程的令牌信息:
accesschk.exe -p <PID>这个工具的好处是它会实际执行访问检查,告诉你"有效权限"是什么,而不是简单地列出DACL。DACL里可能有多个ACE,经过Deny优先、组匹配等规则后,最终的有效权限可能和你想的不一样。
4.4 特权:绕过访问检查的"后门"
有些操作光靠DACL是做不到的,需要特权(Privilege)。特权是令牌里的一个列表,每个特权对应一种系统级操作能力。
常见的特权:
| 特权 | 能做什么 |
|---|---|
| SeDebugPrivilege | 调试任意进程,包括系统进程 |
| SeBackupPrivilege | 备份文件,绕过DACL读取 |
| SeRestorePrivilege | 恢复文件,绕过DACL写入 |
| SeTakeOwnershipPrivilege | 夺取任意对象的所有权 |
| SeLoadDriverPrivilege | 加载内核驱动 |
| SeShutdownPrivilege | 关闭系统 |
特权和DACL的关系是:特权可以绕过DACL检查。比如你有了SeBackupPrivilege,即使DACL不给你读权限,你也能读文件。这就是为什么备份程序能以普通用户身份备份整个系统。
但特权默认是禁用状态,需要显式启用。在代码里用AdjustTokenPrivileges启用特权:
TOKEN_PRIVILEGES tp; tp.PrivilegeCount = 1; LookupPrivilegeValue(NULL, SE_BACKUP_NAME, &tp.Privileges[0].Luid); tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);实操心得:排查"为什么备份程序能读我没权限读的文件"这类问题时,先看进程令牌里有没有启用备份特权。很多安全软件也是靠特权来实现文件扫描的。
5. 实战排查:从现象到根因的完整链路
5.1 案例一:服务写入日志失败
回到开头那个案例。服务以LocalSystem运行,往D:\Logs写文件失败。排查步骤:
第一步:确认进程令牌
用Process Explorer看服务进程的Security标签,确认令牌里的用户SID是SYSTEM,组列表里有哪些组。
第二步:查看目录DACL
icacls D:\Logs输出显示Everyone:(F),看起来没问题。
第三步:用accesschk验证有效权限
accesschk.exe -d "NT AUTHORITY\SYSTEM" D:\Logs结果发现SYSTEM对该目录只有读权限,没有写权限。为什么?因为DACL里虽然给了Everyone完全控制,但可能有一个Deny ACE拒绝了SYSTEM的写入。仔细看icacls的完整输出,果然有一行:
NT AUTHORITY\SYSTEM:(DENY)(W)这就是根因。Deny优先,即使Everyone允许了也没用。
第四步:修复
移除那个Deny ACE,或者调整ACE顺序(但Deny优先是规则,改顺序没用,必须删掉Deny ACE)。
5.2 案例二:模拟身份后访问远程资源失败
一个Web服务模拟调用者身份去访问远程文件共享,本地测试没问题,远程就失败。
排查思路:
本地能成功说明模拟本身没问题。远程失败通常是因为模拟级别不够。SecurityImpersonation级别的令牌只能在本地使用,要访问远程资源需要SecurityDelegation级别。
但SecurityDelegation需要域环境支持委派,而且配置复杂。另一个方案是不使用模拟,直接用服务账户访问远程资源,然后给服务账户授予远程共享的权限。
验证方法:
在代码里打印当前线程令牌的模拟级别:
WindowsIdentity identity = WindowsIdentity.GetCurrent(); Console.WriteLine($"ImpersonationLevel: {identity.ImpersonationLevel}");如果输出是Impersonation而不是Delegation,那就确认了问题所在。
5.3 案例三:注册表键访问被拒绝
一个安装程序需要写HKEY_LOCAL_MACHINE\SOFTWARE\SomeApp,非管理员运行时失败。
排查:
HKLM下的键默认只有管理员能写。非管理员进程的令牌里,Administrators组被标记为Deny-only,所以写不了。
解决方案:
- 方案一:安装程序要求以管理员身份运行(加manifest)
- 方案二:提前给
Users组授予该注册表键的写权限(安装时由管理员做一次) - 方案三:改用
HKEY_CURRENT_USER,不需要管理员权限
5.4 常见问题速查表
| 现象 | 可能原因 | 排查工具 |
|---|---|---|
| 明明有权限却访问被拒 | Deny ACE优先 | icacls, accesschk |
| 管理员账户操作失败 | UAC令牌过滤 | whoami /all |
| 模拟后远程访问失败 | 模拟级别不够 | 代码打印ImpersonationLevel |
| 子目录权限和父目录不一致 | 继承被阻断 | icacls查看继承标志 |
| 服务账户访问失败 | 服务令牌缺少必要组 | Process Explorer |
| 备份程序能读无权限文件 | 备份特权绕过DACL | accesschk -p |
| 复制文件后权限变了 | 复制继承目标目录权限 | icacls对比源和目标 |
6. 工具链和代码层面的实操建议
6.1 必备工具清单
做Windows权限排查,这几个工具基本是标配:
- Process Explorer:看进程令牌、句柄、加载的DLL
- accesschk:命令行验证有效权限
- icacls:查看和修改文件/目录ACL
- whoami /all:快速查看当前令牌
- PowerShell Get-Acl/Set-Acl:脚本化操作ACL
- auditpol:配置审计策略
- 事件查看器:查看审计日志
这些工具都是微软官方提供的,不需要额外安装(除了Sysinternals套件需要下载)。
6.2 代码里操作安全描述符的注意事项
在C#里操作ACL,有几个坑:
坑一:修改ACL需要对象的WRITE_DAC权限
如果你没有WRITE_DAC权限,Set-Acl会失败。这时候要么用管理员权限,要么先夺取所有权。
坑二:ACE顺序很重要
虽然Deny优先是规则,但Allow ACE之间的顺序不影响结果(权限是累积的)。不过为了可读性,通常把Deny ACE放在前面。
坑三:继承标志要设对
// 正确的继承标志设置 FileSystemAccessRule rule = new FileSystemAccessRule( identity, rights, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow );如果只设ContainerInherit,子文件不会继承;只设ObjectInherit,子目录不会继承。
坑四:修改ACL后要刷新
有时候修改了ACL,但已经打开的句柄不会重新检查权限。需要关闭再重新打开。
6.3 权限设计的最佳实践
从防御的角度,设计权限时建议:
- 最小权限原则:只给必要的权限,不要图省事给Everyone完全控制
- 用组而不是用户:给组授权,用户加入组,便于管理
- 慎用Deny:Deny ACE容易造成排查困难,能用Allow解决的尽量用Allow
- 继承要规划好:在顶层目录设计好继承结构,避免逐层修改
- 定期审计:用
accesschk或PowerShell脚本定期扫描关键目录的ACL,发现异常权限
6.4 一个实用的PowerShell审计脚本
# 扫描指定目录下所有文件的ACL,找出权限过宽的条目 $path = "C:\CriticalFolder" Get-ChildItem -Path $path -Recurse | ForEach-Object { $acl = Get-Acl $_.FullName foreach ($ace in $acl.Access) { if ($ace.IdentityReference -match "Everyone" -and $ace.FileSystemRights -match "FullControl|Write|Modify") { [PSCustomObject]@{ File = $_.FullName Identity = $ace.IdentityReference Rights = $ace.FileSystemRights Type = $ace.AccessControlType } } } }这个脚本会列出所有给了Everyone写权限的文件,方便你快速定位需要收紧的权限。
实操心得:权限审计不要一次性全做,容易把自己锁在外面。建议先在测试环境验证脚本,确认没问题再在生产环境跑。另外,修改ACL之前一定要备份原始ACL,用
icacls /save可以导出。
7. 几个容易混淆的概念澄清
7.1 令牌 vs 会话 vs 登录
这三个概念经常被混用,但含义不同:
- 登录会话(Logon Session):用户登录一次就是一个会话,有唯一的LUID
- 令牌(Token):登录会话的产物,包含身份和权限信息
- 会话(Session):Windows的会话隔离机制,比如Session 0是服务会话,Session 1是第一个交互登录用户
一个登录会话可以产生多个令牌(比如UAC过滤前后的两个令牌)。一个令牌可以关联到多个进程。会话和令牌是多对多的关系。
7.2 DACL为空 vs 没有DACL
这两个状态完全不同:
- DACL为空(
DACL Present但ACE数量为0):拒绝所有访问 - 没有DACL(
DACL NULL):允许所有访问
用icacls看,空DACL显示为空,没有DACL显示为(NULL)或者不显示DACL部分。
这个区别在排查问题时很重要。有些对象默认没有DACL,任何人都能访问;有些对象DACL为空,谁都访问不了。
7.3 所有者权限
对象的所有者永远有WRITE_DAC权限,即使DACL里没有显式授予。这意味着所有者可以随时修改DACL,给自己加权限。
这就是为什么takeown命令能夺取文件所有权——夺取后你就是所有者,然后就能改DACL了。
但夺取所有权需要SeTakeOwnershipPrivilege特权,普通用户默认没有。管理员有这个特权,所以管理员能夺取任何文件的所有权。
7.4 完整性级别
完整性级别(Integrity Level)是Vista之后引入的强制访问控制机制,和DACL是独立的。它分几个级别:
- Low:最低,比如浏览器沙箱
- Medium:普通用户进程
- High:管理员进程
- System:系统进程
完整性级别的规则是:低完整性进程不能写高完整性对象,即使DACL允许。这就是为什么IE的保护模式(Low完整性)不能写用户目录(Medium完整性)的文件。
完整性级别在令牌里,用whoami /all能看到:
Mandatory Label\Medium Mandatory Level排查权限问题时,如果DACL看起来没问题但还是访问被拒,就要检查完整性级别。
8. 从攻击面看安全机制的设计取舍
理解安全机制不仅要会防守,也要知道攻击者会怎么绕过。这样你才能设计出更靠谱的权限方案。
8.1 令牌窃取和模拟
攻击者拿到一个高权限进程的令牌后,可以用DuplicateTokenEx复制令牌,然后用CreateProcessWithTokenW以那个令牌启动新进程。这就是所谓的"令牌窃取"。
防御措施:
- 限制
SeDebugPrivilege的授予 - 启用PPL(Protected Process Light)保护关键进程
- 监控异常的令牌复制行为
8.2 ACL劫持
如果某个高权限进程会加载一个低权限用户可写的DLL,攻击者可以替换那个DLL,让高权限进程加载恶意代码。这是DLL劫持的一种。
防御措施:
- 确保高权限进程加载的DLL路径不可被低权限用户写入
- 使用安全DLL搜索顺序
- 启用
SafeDllSearchMode
8.3 特权滥用
SeBackupPrivilege和SeRestorePrivilege是双刃剑。备份程序需要它们,但攻击者拿到这些特权后也能绕过DACL读敏感文件。
防御措施:
- 只给必要的账户授予这些特权
- 监控特权的使用
- 考虑用专用备份账户,而不是给普通管理员授予
8.4 完整性级别的绕过
完整性级别不是万能的。有些操作可以绕过完整性检查,比如通过命名管道、共享内存等IPC机制。攻击者可能利用这些通道从低完整性进程向高完整性进程传递数据。
防御措施:
- 对IPC通道也做权限检查
- 不要假设低完整性进程完全无害
- 关键操作在高完整性进程里做二次验证
9. 我个人的一些经验体会
搞Windows安全机制这些年,最大的感受是:大部分权限问题都不是"权限不够",而是"权限不对"。你以为需要加权限,实际上可能是令牌里缺了某个组,或者有个Deny ACE在作祟,或者完整性级别不匹配。
排查权限问题的顺序,我一般是这样:
- 先看令牌:
whoami /all或者Process Explorer,确认当前身份和组列表 - 再看DACL:
icacls看完整输出,特别注意Deny ACE - 然后验证有效权限:
accesschk实际执行访问检查 - 最后看完整性级别和特权:这两个是容易被忽略的因素
还有一点:不要在生产环境随便改ACL。改之前先备份,改之后立即验证。我见过太多人改ACL把自己锁在外面,最后只能进安全模式恢复。
另外,PowerShell的Get-Acl和Set-Acl虽然方便,但有些细节和icacls不完全一致。比如继承标志的处理,PowerShell的InheritanceFlags和icacls的(OI)(CI)对应关系需要搞清楚。建议关键操作还是用icacls,脚本化的时候再用PowerShell。
最后分享一个我常用的排查命令组合,基本能覆盖80%的场景:
whoami /all icacls <目标路径> accesschk.exe -d -u <用户名> <目标路径>这三条命令跑完,大部分权限问题的根因就清楚了。如果还不行,再上Process Explorer看令牌细节和完整性级别。