《Windows Internals》10.2.9 最小权限运行:为什么服务不应该“账户有什么权限就全拿到”?
文章目录
- 《Windows Internals》10.2.9 最小权限运行:为什么服务不应该“账户有什么权限就全拿到”?
- 1. 先说结论:最小权限运行解决的是“服务 Token 过大”的问题
- 2. 为什么服务要坚持“最小权限”?这是降低攻击面的基本原则
- 3. 最小权限运行是如何配置的?
- 4. 独立服务进程与共享服务进程的权限处理有什么区别?
- 4.1 独立服务进程:可以更彻底地精简 Token
- 4.2 共享服务进程:权限是多个服务需求的并集
- 5. 如果没有 RequiredPrivileges,会发生什么?
- 6. 如何查看服务声明了哪些 RequiredPrivileges?
- 7. 如何验证服务声明的权限是否真的进入 Token?
- 7.1 第一步:使用 sc.exe 查看服务声明权限
- 7.2 第二步:在任务管理器中定位服务对应 PID
- 7.3 第三步:用 Process Explorer 查看 Token 权限
- 8. 从桌面支持角度,最小权限运行有什么实际意义?
- 8.1 排查服务权限问题时,不要只看账户
- 8.2 共享 svchost 的权限不等于某一个服务的权限
- 8.3 服务最小权限配置可以作为安全加固检查项
- 9. 常用命令整理:服务权限排查可以这样做
- 9.1 查看服务声明权限
- 9.2 查看服务详细配置
- 9.3 查看服务对应进程 ID
- 9.4 查看某个 svchost 托管了哪些服务
- 9.5 查看服务注册表 RequiredPrivileges
- 10. 用一张图理解 SCM 如何生成最小权限 Token
- 11. 最容易误解的 6 个点
- 11.1 误区一:服务运行账户有什么权限,服务就一定拿到全部权限
- 11.2 误区二:RequiredPrivileges 是给服务增加权限
- 11.3 误区三:独立服务和共享服务的最小权限效果完全一样
- 11.4 误区四:没有 RequiredPrivileges 就更安全
- 11.5 误区五:sc qprivs 看到的就是进程 Token 的全部权限
- 11.6 误区六:普通用户不需要理解服务权限
- 12. 我的学习理解:最小权限运行真正改变的是服务安全边界
- 13. 总结提升
- 下一篇预告
《Windows Internals》10.2.9 最小权限运行:为什么服务不应该“账户有什么权限就全拿到”?
在学习《Windows Internals》10.2.9 Running with least privilege(最小权限运行)这一节时,我觉得它对企业桌面支持、Windows 运维、安全加固都非常有价值。
很多人理解 Windows 服务时,容易停留在一个比较表层的认识:
服务以某个账户运行,所以这个账户有什么权限,服务进程就能拿到什么权限。
这个理解不能说完全错,但它只适合描述传统的all-or-nothing 大权限模型。
从安全角度看,这种模型的问题非常明显:服务真正只需要 3 个权限完成任务,却因为账户本身拥有 10 个权限,最后整个服务进程也拿到了 10 个权限。
这就会带来一个典型风险:
服务一旦被利用,攻击者能利用的权限范围也会被放大。
而 Windows 的最小权限运行机制,就是为了解决这个问题:
让服务开发者可以声明服务真正需要哪些权限,SCM 在服务启动时根据这些声明生成更精简的 Token,从而降低服务进程的攻击面。
这篇文章就围绕10.2.9 最小权限运行,从原理、配置流程、独立服务与共享服务差异、RequiredPrivileges 缺失风险、实验验证方法几个角度,把这一节讲清楚。
1. 先说结论:最小权限运行解决的是“服务 Token 过大”的问题
Windows 服务通常运行在某个账户上下文中,比如:
LocalSystemLocalServiceNetworkService- 指定域账户
- 虚拟服务账户
NT SERVICE\<ServiceName>
传统模式下,服务进程拿到的权限主要取决于它运行账户本身拥有哪些权限。
这就会形成一个很典型的all-or-nothing 模型:
账号有什么权限,服务进程就可能拿到什么权限。
但从安全设计角度看,这并不理想。
因为一个服务真实需要的权限,往往只是账户权限集合中的一小部分。
例如某个服务可能只需要:
SeChangeNotifyPrivilegeSeCreateGlobalPrivilegeSeImpersonatePrivilege
但如果服务账户还有更多权限,例如:
SeBackupPrivilegeSeRestorePrivilegeSeDebugPrivilegeSeLoadDriverPrivilege
那服务进程一旦拿到完整 Token,安全风险就明显变高。
最小权限运行的核心思想就是:
服务只应该拥有完成任务所需的最少权限,而不是继承账户拥有的全部权限。
2. 为什么服务要坚持“最小权限”?这是降低攻击面的基本原则
在企业桌面运维和系统安全里,有一个非常重要的原则:
Principle of Least Privilege,最小权限原则。
它的含义很简单:
一个进程、服务、用户或组件,只应该拥有完成当前任务所必需的权限,不应该额外拥有无关权限。
这句话看起来像安全口号,但在服务模型中非常现实。
假设一个服务本来只负责证书、加密或系统组件管理,它正常工作只需要几个特定权限。
如果它额外拥有调试权限、加载驱动权限、备份还原权限,一旦这个服务出现漏洞,攻击者就可能借服务进程的 Token 做更多高危操作。
所以最小权限运行带来的价值主要有三点:
- 降低攻击面:服务被利用后,可用权限更少。
- 减少横向扩大风险:攻击者不能轻易借服务 Token 做更多系统级操作。
- 提升服务隔离效果:每个服务只拿自己需要的权限,不把账户权限整体暴露出来。
这也是为什么我认为这一节非常适合桌面支持工程师学习。
因为它能帮我们从“服务能不能启动”进一步理解到:
服务以什么权限启动,才是安全运维真正要关注的问题。
3. 最小权限运行是如何配置的?
Windows 并不是让 SCM 自己猜测某个服务需要哪些权限。
更合理的做法是:
由服务开发者显式声明服务所需权限,然后 SCM 在服务启动时读取这些配置。
核心流程可以理解为:
对应到具体机制,就是服务开发者使用:
ChangeServiceConfig2(hService,SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO,&RequiredPrivilegesInfo);这个 API 会把服务声明的权限写入服务注册表项中的:
RequiredPrivileges典型路径类似:
HKLM\SYSTEM\CurrentControlSet\Services\<ServiceName>也就是说,服务真正需要哪些权限,并不是写在代码注释里,也不是靠管理员记忆,而是被持久化到了服务对应的注册表配置中。
当服务启动时:
- SCM 读取服务配置;
- 检查
RequiredPrivileges值; - 根据服务账户本身具备的权限进行裁剪;
- 创建只包含必要权限的 Token;
- 用这个 Token 启动服务进程或服务宿主进程。
这里有一个关键限制:
服务声明的权限必须是服务账户本身已经拥有权限的子集。
也就是说,RequiredPrivileges不是“凭空申请新权限”的地方。
它只能告诉 SCM:
我只需要账户已有权限中的这些权限,请帮我把其他无关权限剥离掉。
4. 独立服务进程与共享服务进程的权限处理有什么区别?
这一节里非常关键的一点是:
独立服务进程和共享服务进程的最小权限处理方式不完全一样。
4.1 独立服务进程:可以更彻底地精简 Token
如果一个服务是独立进程运行,比如:
Service.exe那么 SCM 可以比较直接地为它创建一个精简 Token。
这个 Token 只需要包含该服务声明的RequiredPrivileges。
这种情况下,最小权限效果最好。
可以简单理解为:
服务账户完整权限集合 ↓ SCM 根据 RequiredPrivileges 裁剪 ↓ 服务进程 Token 只保留必需权限也就是说:
独立服务进程更容易做到真正意义上的权限最小化。
4.2 共享服务进程:权限是多个服务需求的并集
但很多 Windows 服务并不是每个服务一个独立进程。
大量服务会运行在共享宿主进程中,最典型的就是:
svchost.exe例如一个svchost.exe中可能托管多个服务:
- Service A
- Service B
- Service C
如果这些服务都声明了自己的RequiredPrivileges,SCM 不能只按其中一个服务的权限来创建 Token。
因为同一个宿主进程要同时服务多个服务。
所以 SCM 会计算:
所有宿主服务 RequiredPrivileges 的并集。
也就是说:
Service A 需要权限集合 Service B 需要权限集合 Service C 需要权限集合 ↓ 取并集 ↓ 生成 svchost.exe 的 Token这就意味着:
共享服务进程中,只会移除所有宿主服务都不需要的权限。
这也是为什么共享宿主进程虽然节省资源,但在最小权限隔离上天然不如独立服务进程彻底。
5. 如果没有 RequiredPrivileges,会发生什么?
这是这一节中特别值得注意的风险点。
如果某个服务没有配置:
RequiredPrivileges那 SCM 就没有办法准确知道这个服务到底需要哪些权限。
此时 SCM 只能采取保守策略:
认为该服务可能不兼容最小权限模型,或者可能需要账户全部权限才能正常运行。
结果就是:
SCM 会创建完整 Token,而不是最小权限 Token。
这就会导致一个安全后果:
- 服务仍然能运行;
- 但不会获得最小权限模型带来的额外安全收益;
- 服务可能拥有比实际需求更多的权限;
- 服务一旦被利用,攻击面会扩大。
所以从安全最佳实践来看:
只要服务能够明确自己的权限需求,就应该显式配置 RequiredPrivileges。
如果服务想尽可能剥离权限,理论上可以只声明非常基础的权限,例如:
SeChangeNotifyPrivilege这个权限通常用于绕过遍历检查,是很多 Windows 组件中非常基础、常见的权限。
6. 如何查看服务声明了哪些 RequiredPrivileges?
这一节的实验非常适合我们做 Windows 运维时验证服务权限。
最直接的命令是:
sc qprivs <服务名>例如查看CryptSvc服务声明的权限:
sc qprivs cryptsvc你可能会看到类似结果:
SERVICE_NAME: cryptsvc PRIVILEGES REQUIRED: ----------------------------- SeChangeNotifyPrivilege SeCreateGlobalPrivilege SeImpersonatePrivilege这说明CryptSvc声明自己需要的权限主要包括:
| 权限名称 | 通俗理解 |
|---|---|
SeChangeNotifyPrivilege | 绕过遍历检查 |
SeCreateGlobalPrivilege | 创建全局对象 |
SeImpersonatePrivilege | 模拟客户端身份 |
这个命令查看的是服务在配置层声明的最小权限集合,也就是来自注册表中的RequiredPrivileges值。
7. 如何验证服务声明的权限是否真的进入 Token?
只看sc qprivs还不够。
因为它只能说明服务声明了哪些权限。
如果要进一步验证这些权限是否进入了服务进程 Token,可以结合三个工具:
- sc.exe
- 任务管理器
- Process Explorer
7.1 第一步:使用 sc.exe 查看服务声明权限
以CryptSvc为例:
sc qprivs cryptsvc确认输出中包含:
SeChangeNotifyPrivilege SeCreateGlobalPrivilege SeImpersonatePrivilege7.2 第二步:在任务管理器中定位服务对应 PID
打开任务管理器:
任务管理器 → 服务找到:
CryptSvc查看它对应的 PID。
因为CryptSvc通常由svchost.exe托管,所以我们需要先知道它到底运行在哪一个svchost.exe实例里。
7.3 第三步:用 Process Explorer 查看 Token 权限
打开 Sysinternals 的Process Explorer,以管理员身份运行。
找到对应 PID 的svchost.exe,双击打开属性。
进入:
Security → Privileges查看当前进程 Token 中有哪些权限,以及哪些权限处于启用或禁用状态。
如果你看到CryptSvc声明的权限出现在 Token 里,就说明:
服务配置中的 RequiredPrivileges 已经被 SCM 用于构造服务宿主进程 Token。
8. 从桌面支持角度,最小权限运行有什么实际意义?
这一节不是纯理论。
对企业桌面支持、终端运维、安全排障都有现实价值。
8.1 排查服务权限问题时,不要只看账户
以前我们可能会这样判断:
这个服务跑在 NetworkService 下,所以它大概有什么权限。
但学完这一节后,判断应该更细:
- 服务运行账户是什么;
- 服务是否配置
RequiredPrivileges; - 如果是共享宿主进程,宿主中还有哪些服务;
- 最终进程 Token 中实际有哪些权限。
也就是说:
服务账户只是第一层,最终 Token 才是实际运行权限。
8.2 共享 svchost 的权限不等于某一个服务的权限
如果一个svchost.exe里托管多个服务,那么这个进程 Token 可能包含多个服务权限需求的并集。
所以你在 Process Explorer 里看到的权限,不一定完全属于某一个服务。
这也是为什么做排障时要结合:
tasklist /svc查看某个进程中托管了哪些服务:
tasklist /svc /fi "imagename eq svchost.exe"或者用 PowerShell 查看:
Get-CimInstanceWin32_Service|Select-ObjectName,DisplayName,State,ProcessId,StartName|Sort-ObjectProcessId8.3 服务最小权限配置可以作为安全加固检查项
在企业环境中,如果我们要做服务安全检查,可以关注:
- 高权限账户下运行的服务;
- 没有配置 RequiredPrivileges 的服务;
- 运行在共享宿主中的高风险服务;
- 使用
LocalSystem但实际权限需求很少的服务; - 第三方软件服务是否过度申请权限。
这类检查可以沉淀为桌面运维安全基线。
9. 常用命令整理:服务权限排查可以这样做
下面这些命令非常适合日常排障使用。
9.1 查看服务声明权限
sc qprivs cryptsvc通用格式:
sc qprivs <服务名>9.2 查看服务详细配置
sc qc cryptsvc9.3 查看服务对应进程 ID
Get-CimInstanceWin32_Service-Filter"Name='CryptSvc'"|Select-ObjectName,DisplayName,State,ProcessId,StartName9.4 查看某个 svchost 托管了哪些服务
tasklist /svc /fi "imagename eq svchost.exe"9.5 查看服务注册表 RequiredPrivileges
$serviceName="CryptSvc"$path="HKLM:\SYSTEM\CurrentControlSet\Services\$serviceName"Get-ItemProperty-Path$path-Name RequiredPrivileges-ErrorAction SilentlyContinue|Select-Object-ExpandProperty RequiredPrivileges如果没有输出,说明该服务可能没有配置RequiredPrivileges值。
10. 用一张图理解 SCM 如何生成最小权限 Token
可以把整个过程抽象成下面这张逻辑图:
这张图最关键的地方在于:
SCM 不是无条件创建完整 Token,而是会根据 RequiredPrivileges 和服务宿主方式决定如何裁剪权限。
11. 最容易误解的 6 个点
11.1 误区一:服务运行账户有什么权限,服务就一定拿到全部权限
不一定。
如果服务配置了RequiredPrivileges,SCM 可以创建更精简的 Token。
11.2 误区二:RequiredPrivileges 是给服务增加权限
不对。
它不是“申请额外权限”,而是从服务账户已有权限中声明需要保留的子集。
声明的权限必须是服务账户已有权限的子集。
11.3 误区三:独立服务和共享服务的最小权限效果完全一样
不一样。
独立服务可以更彻底裁剪;共享服务进程需要计算多个服务 RequiredPrivileges 的并集。
11.4 误区四:没有 RequiredPrivileges 就更安全
恰恰相反。
没有这个值时,SCM 无法判断服务最小权限需求,只能保守创建完整 Token。
11.5 误区五:sc qprivs 看到的就是进程 Token 的全部权限
不完全是。sc qprivs查看的是服务声明的权限。
进程实际 Token 还要结合宿主方式、账户权限以及同宿主其他服务共同判断。
11.6 误区六:普通用户不需要理解服务权限
不对。
对于桌面支持工程师来说,理解服务权限可以帮助排查:
- 服务启动失败;
- 服务访问资源失败;
- 第三方安全软件行为异常;
- svchost 权限过大;
- 系统服务被限制后功能异常。
12. 我的学习理解:最小权限运行真正改变的是服务安全边界
我觉得10.2.9 最小权限运行最有价值的地方,不是让我记住几个 API 或注册表值,而是让我真正理解:
服务安全不能只看“它用哪个账户运行”,还要看“它最终拿到了一个怎样的 Token”。
以前我们看服务,可能只会关注:
- 服务是否运行;
- 启动类型是什么;
- 运行账户是谁;
- 是否在 svchost 中。
但学完这一节后,我会多看一层:
- 服务是否声明了
RequiredPrivileges; - 声明的权限是否合理;
- 最终进程 Token 是否真的被裁剪;
- 如果是共享进程,是否存在权限并集导致的额外暴露。
这才是从“会看服务”走向“懂服务安全模型”的关键一步。
最小权限运行的本质,不是让服务更难运行,而是让服务只带着完成任务所需的最小能力运行。
13. 总结提升
如果让我用一句话总结《Windows Internals》10.2.9 Running with least privilege,我会这样说:
Windows 服务的最小权限运行机制,是由服务开发者通过 RequiredPrivileges 声明服务所需权限,再由 SCM 在服务启动时根据运行账户、宿主方式和权限声明生成更精简 Token 的安全机制;它的目标不是给服务更多能力,而是尽量剥离服务不需要的权限,从而降低攻击面。
这篇最值得记住的 8 个结论是:
- 传统服务权限模型容易形成 all-or-nothing 问题。
- 最小权限原则要求服务只拥有完成任务所需的权限。
- 服务开发者可通过
ChangeServiceConfig2配置 RequiredPrivileges。 - RequiredPrivileges 会保存到服务注册表键中。
- 独立服务进程可以创建只包含该服务所需权限的精简 Token。
- 共享服务进程需要计算多个服务 RequiredPrivileges 的并集。
- 如果没有 RequiredPrivileges,SCM 通常只能创建完整 Token。
sc qprivs、任务管理器、Process Explorer 可以组合验证服务权限声明与实际 Token。
我觉得这一节最值得沉淀成一句自己的话:
服务账户决定“最多能有什么权限”,RequiredPrivileges 决定“实际应该保留哪些权限”,最终 Token 决定“服务真正带着什么权限运行”。
下一篇预告
下一篇可以继续写:
《Windows Internals》10.2.10 Service isolation:为什么 Service SID 能让服务不再只依赖账户隔离,而是拥有属于自己的安全身份?》
这篇可以继续把:
- Service SID
- 服务隔离
- ACL 精确授权
- 虚拟服务账户
- LocalSystem 权限过大的问题
- 服务资源访问控制
全部串起来。
🔝 返回顶部
点击回到顶部
