1. 令牌机制与提权原理
1.1 什么是访问令牌,为什么它和权限挂钩
做Windows安全研究的人,几乎每天都要跟“令牌”打交道。很多刚入门的朋友第一次接触这个概念时会懵:明明我登录的是管理员账户,为什么很多操作还是提示“拒绝访问”?打开任务管理器看到一堆进程,凭什么有些进程能杀,有些进程点“结束任务”就报错?这些问题,归根结底都要从访问令牌说起。
访问令牌(Access Token)是Windows系统里用来描述进程或线程安全身份的一个核心数据结构。简单打个比方:令牌就像你进高级写字楼时前台给你发的一张临时门禁卡,卡片上写清楚了你的姓名、所属公司、能进哪些楼层、哪些房间能刷开、哪些房间进去会被保安拦。操作系统在做任何“能不能干这件事”的判断时,不看“你是谁”,只看“你手里的令牌能干什么”。
具体到Windows内部,每个进程在启动时都会挂一个主令牌(Primary Token),令牌里头存着一堆关键信息:用户SID、所属组SID、特权列表、完整性级别、默认DACL等等。当你对某个文件、注册表键、进程对象执行操作时,内核的权限检查组件会把令牌里的SID和对象的安全描述符做比对,再加上特权列表里的SeDebugPrivilege这类高级特权,最终才决定是放行还是拒绝。
所以“伪造令牌提权”这个命题的本质,就是制造一个包含更高权限(比如System或Administrator)身份的令牌,然后让目标进程或线程换上这个令牌去干活。Windows确实提供了一组合法的API用于令牌操作,比如OpenProcessToken、DuplicateTokenEx、ImpersonateLoggedOnUser、SetThreadToken,这些API本身是给服务程序、身份模拟场景设计的,但很多安全工具在作恶时同样喜欢走这条路。
1.2 令牌伪造为什么能做到提权
核心原因在于Windows的权限检查机制对令牌来源的信任。系统默认认为:只要你手里的令牌是合法获得的、签名完整、权限检查没过期,那就按照令牌内容来执行,不会每次操作都追溯“这个令牌到底是怎么来的”。于是攻击面就出现了——如果你能从一个高权限进程中复制出它的令牌,或者通过某种方式让低权限进程“借”到高权限令牌,那么你的操作权限就跟着抬上去了。
举个例子:某服务以SYSTEM身份运行,普通用户对这个服务进程有进程级的某些访问权限(比如调试权限,或者某些配置不当的注入通道)。攻击者可先打开该进程,拿到它的令牌句柄,再用DuplicateTokenEx复制出一个具有模拟级别的主令牌,最后用CreateProcessWithTokenW拉起一个子进程。这个子进程的令牌里写着SYSTEM的SID和几乎所有特权,于是子进程就能做系统级操作,比如读取SAM数据库、修改服务配置、写入受保护目录等。
还有一个现象值得注意:同样是拿到一个高权限令牌,未必就能为所欲为。Windows从Vista开始加入了完整性级别(Integrity Level)机制,低完整性进程哪怕拿着高权限令牌,访问中完整性对象时也可能被拒绝,这就是很多人在本地提权测试时发现“令牌已经是System了但某些文件还是打不开”的原因之一。伪造令牌做的事,本质上是把令牌的“内容”做假或做借,但如果令牌的完整性级别不够,照样会碰壁。
2. 伪造令牌的关键前置概念
2.1 令牌类型:主令牌与模拟令牌
要做令牌伪造,第一步就得把Windows里两种令牌类型分清楚。主令牌(Primary Token)是进程的“身份证”,它决定了整个进程的访问身份,进程启动之后就一直挂在进程对象上,直到进程结束。模拟令牌(Impersonation Token)则是线程在用的一种临时身份切换工具,线程可以在一段时间内把自己的安全上下文切换成另一个用户的令牌,干完活再恢复回来。
很多人会混淆这两个概念,其实有一条简单规则:CreateProcess系列创建进程时用的是主令牌;SetThreadToken、ImpersonateLoggedOnUser这类接口操作的是模拟令牌。TokenImpersonation级别的模拟令牌还能进一步指定模拟程度,从SecurityAnonymous到SecurityDelegation一共四级,级别越高,模拟方对目标上下文的使用权限就越大。
做令牌伪造时,常见路径是:先获取目标进程的令牌句柄,用DuplicateTokenEx复制出一个全新的主令牌(可以指定TokenPrimary),然后拿这个主令牌去创建进程。也有另一种思路,从一个已登录的高权限用户会话里抓到模拟令牌,然后直接SetThreadToken给自己的线程穿上,方便在同一个进程里完成操作,这样连进程创建都省了,行为上更难被常规命令捕获。
2.2 权限级别与特权项:从普通用户到System
Windows里的用户权限大概可以按层级排一条线:普通用户(Users)→ 管理员(Administrators)→ SYSTEM → 内核。伪造令牌提权,绝大多数场景的目标是拿到SYSTEM或至少管理员级别的令牌。
SYSTEM账户严格来说不是“用户”,它是Windows内核组件和服务默认使用的安全主体,对系统几乎所有对象都有完全控制权。它比管理员还“大”,原因在于它属于系统自带账户,很多核心系统进程的DACL都只向SYSTEM开放。如果你能拿到一个SYSTEM进程的令牌,那在ring3层就基本没有干不了的事了。
令牌里还有一个不能忽视的部分:特权列表。就算你的令牌里写了管理员,如果SeDebugPrivilege是禁用状态,很多进程调试类操作照样做不了。所以很多提权工具会自动启用令牌里的特权项,用AdjustTokenPrivileges把SeDebugPrivilege、SeImpersonatePrivilege等变成启用状态。特权项就是令牌的“门禁卡等级”,SID是你能去哪,特权是你能对东西动手到什么程度,两者是互相配合的。
3. 伪造令牌的常见路径与实操还原
3.1 令牌复制:从高权限进程“借”身份
令牌复制是伪造令牌最直接的一条路。我在实际测试中走得最多的流程是这样的:
先用OpenProcess打开一个已经存在的系统进程,比如某些以SYSTEM身份运行的服务进程。这一步对句柄权限有要求,通常需要PROCESS_QUERY_INFORMATION,改一下就能获得查询令牌的权限。然后调用OpenProcessToken打开该进程的令牌句柄。
重点来了,DuplicateTokenEx的作用是把打开的令牌句柄复制出一个新的令牌对象。你可以指定它变成主令牌,并设置TokenPrimary级别。复制出来的令牌内容跟原令牌几乎一模一样,包括SID组、特权列表、默认DACL,唯一的区别是这个令牌不再绑定原来的进程生命周期,你可以拿它做任何事。
拿到令牌副本之后,用CreateProcessWithTokenW创建新进程,新进程就会自动带上这份高权限令牌。如果不想创建新进程,也可以走CreateProcessAsUser的路线,把桌面和窗口站等信息也一并继承过来,这样新进程还能正常跟桌面交互,不会出现弹不出窗口的情况。
这里说一句大实话:这套API链路本身是Microsoft为兼容性和服务交互场景提供的正常能力,比如IIS应用程序池切换身份就大量使用类似逻辑。但在攻防演练里,攻击者会把它们的用途榨干,拿来做横向移动和本地提权的利器。防御方真正应该盯的不是API本身,而是调用链条和上下文是否合理。
3.2 令牌模拟:不创建进程也能提权
第二种常见路径是模拟令牌,核心API有ImpersonateLoggedOnUser、SetThreadToken、ImpersonateNamedPipeClient。它的思路不是复制出一个新进程,而是让当前进程里的线程临时换一张“工牌”。
这类操作最常见的使用场景是服务端程序模拟客户端身份。比如一个Windows服务接收客户端的连接和请求,服务端线程想以客户端的身份去访问某些资源,就可以用ImpersonateLoggedOnUser把线程令牌切换过去。这个设计原本是为了最小权限和安全边界,攻击者却会反过来用——如果我能抓到一个高权限用户的令牌句柄,我直接模拟它,不创建新进程、不留新的父子进程关系,整个操作都发生在原进程内部,排查难度陡增。
这里还牵扯到一个有趣的利用点:命名管道模拟。一个进程如果创建了一个命名管道并等待客户端连接,客户端连上来并发送数据时,服务端可以调用ImpersonateNamedPipeClient获取客户端的令牌。很多本地提权漏洞利用的就是这项服务的身份切换机制,让一个低权限客户端去连接高权限服务端的管道,从而拿到服务端进程的令牌。这就是所谓“土豆”家族提权手法的核心逻辑。
3.3 实例:以检测视角还原令牌操纵过程
有朋友常问:你们做检测的人,怎么深入了解提权攻击的细节?我的回答是:自己动手做一遍攻击流程,然后用日志和系统监控工具把过程完整记录下来,再逐条分析告警点。前段时间我在某安全研究环境里重新走了一遍流程,用的完全是非破坏性的检测脚本,仅仅读取令牌信息就发现了很多有意思的东西。
下面这段代码演示了如何枚举进程令牌并读取其包含的用户名和特权状态,这不是攻击代码,而是定位异常的起点:
$ProcessList = Get-Process -Name "svchost","lsass" -ErrorAction SilentlyContinue foreach ($Proc in $ProcessList) { try { $Token = Invoke-CimMethod -ClassName Win32_Process -MethodName GetOwner -Arguments @{ ProcessId = $Proc.Id } Write-Host "[*] Process: $($Proc.ProcessName) PID: $($Proc.Id) Owner: $($Token.User)" } catch { Write-Host "[!] Access denied for PID $($Proc.Id)" } }从检测的角度看,这套流程结束后,可以顺手保存进程路径、启动命令行、父进程信息、令牌完整性级别等字段。之后每一条异常告警都会回溯到这些基础数据上,判断是“有人真的在高权限进程上做了不该做的操作”,还是系统服务间的正常交互。关键是记住:令牌伪造的检测侧重点,永远不是API复用,而是调用路径、调用时机和权限滥用间的关联关系。
4. 检测与防御:如何对抗令牌伪造
4.1 从攻击者思路反推检测点
做防御的人必须习惯反着思考:攻击者拿到高权限令牌后,必然要创建进程或修改线程令牌。这个行为会在系统里留下痕迹,比如新进程的Owner和启动者的用户SID不一致、父进程与子进程的令牌差异过大、特权开启后短时间内出现密集的高敏感操作等。
我自己在监控方案里最看重几个检测点,这里整理成表:
| 检测点 | 告警内容 | 说明 |
|---|---|---|
| 进程创建事件 | 新进程的Token Owner与父进程不一致 | 通常是CREATE_WITH_TOKEN的典型信号 |
| 特权启用 | 短时间内SeDebugPrivilege由禁用变为启用 | 提权工具常见前置动作 |
| 敏感进程访问 | 非System用户尝试打开lsass句柄 | 可能涉及令牌窃取或凭据读取 |
| 模块加载 | 可疑DLL注入高权限进程服务 | 常配合令牌伪造做持久化 |
| 行为链 | 低权限用户短时间内出现大量高敏感操作 | 提示操作系统层权限被异常抬升 |
这里特别提醒:单点检测很容易被绕过。最有效的方案是结合Windows事件日志、ETW(Event Tracing for Windows)和进程命令行审计做成一个闭环,把“谁、何时、通过哪条路径、接触了什么对象、产生了什么进程”完整串起来。
4.2 系统加固与最小权限原则
我一直跟团队强调一句话:没有绝对安全的系统,只有相对合理的权限边界。令牌伪造能成功,前面提到过,往往是因为系统里存在配置不当的高权限进程暴露了句柄,或者用户令牌的不当保存和传递给了攻击者可乘之机。
防御上的第一道闸门是限制SeDebugPrivilege的授予范围。开发调试环境和管理员日常办公环境的策略必须分开,生产环境应坚决禁止将SeDebugPrivilege分配给普通运维账号。很多提权测试里,攻击者一旦拿到托管账户权限,马上就会检查自己有没有SeDebugPrivilege,有就直接开调试句柄抓System令牌,一条龙下来畅通无阻。
第二道闸门是启用受保护进程轻量级(PPL)机制。关键杀毒进程、LSASS进程都应该被配置成PPL级别。普通进程没有权限打开PPL进程的句柄,自然也就拿不到里面的令牌或内存信息。实际部署时要注意:盲目开启PPL可能引发第三方安全软件兼容性问题,需要在隔离环境里做充分的回归测试。
第三道闸门是把服务账户改成虚拟账户或托管服务账号(gMSA),避免把高权限令牌长期挂在某个固定用户身上。这样即使攻击者拿到了服务进程的令牌,也无法跳出这个托管身份去访问其他系统资源,横向移动的能力会断掉一大截。
5. 常见问题与排查技巧实录
5.1 为什么伪造令牌后权限还是不够
这个问题我在技术群和线下交流里被问过很多次。拿到SYSTEM令牌,结果发现某些注册表项仍然无法写入,某些文件仍然访问被拒。出现这种情况,多半是遗漏了完整性级别这个维度。
上文提过,完整性级别是独立于用户权限的一套机制。一个进程即使令牌里是SYSTEM SID,如果完整性级别被标记为低(Low),它访问中等完整性(Medium)的常规文件时会被直接拦截。部分提权工具在复制令牌时会重新设置进程的完整性级别,但如果模拟的令牌包含受限SID(Restricted SID),还会触发更严格的约束检查。
排查方法是先确认令牌的完整性级别。用Process Explorer打开进程属性,看到“Integrity Level”一栏是否与预期一致。如果搞不清自己的令牌结构,可以用系统自带的whoami /all或者第三方工具输出令牌的完整信息,逐项对比SID、特权、完整性级别三项内容是否都达标。
5.2 令牌伪造与UAC、隔离机制的关系
UAC(用户账户控制)在令牌伪造过程中是一个很容易把人绕晕的变量。Windows对管理员账户默认是拆分成两个令牌的:一个是完整管理员令牌,另一个是过滤后的标准用户令牌。大部分程序启动时用的是过滤令牌,所以你明明登录的是管理员,很多管理操作却需要弹UAC框再确认一次。
在令牌伪造场景下,如果你从某个进程复制到的令牌本身就是被UAC过滤过的标准用户令牌,那即使操作成功,权限也没有实质提升。这也是为什么各种本机提权工具总是优先寻找那些使用了完整管理员令牌或SYSTEM令牌的高价值进程。建议大家在做权限验证时,别只看是不是管理员,要看完整令牌还是过滤令牌,看得越细越不容易被表象骗到。
5.3 一个实用的排查思路
最后分享一个排查实践中非常顺手的方法。遇到怀疑令牌被伪造的告警时,我会做三件事:
第一步,定位可疑进程,把它的PID、启动命令、父进程ID和进程路径全部拉出来; 第二步,对比该进程的令牌Owner与父进程令牌Owner的差异。正常从资源管理器启动的子进程,Owner应该一致;不一致就重点查它创建时是否走了带有Token Access的API路径; 第三步,检查该进程加载的模块列表和网络连接,看看有没有与可疑服务通信的迹象。这一步能辅助判断攻击者是否会拿这个进程做横向移动跳板。
这套流程走下来,八成以上的可疑告警都能给出比较清楚的判断结果。当然,误报仍然存在,比如反病毒软件更新时间段内会动态拉起临时高权限进程,这类正常运维行为就需要结合变更记录去匹配。
回到令牌本身,我现在做权限相关评估时,会先给对象拍一张“令牌快照”,包含完整SID、特权、完整性级别、受限状态、会话ID这几个关键字段,再谈下一步操作。很多看起来很玄的高权限问题,拆到令牌这一层就变得非常直白——权限不够就找哪里有更高令牌,令牌不对就看哪一步的复制或模拟出了问题。经验多了之后你会发现,Windows的权限体系绕来绕去,绕不开的一直是这个门禁卡结构,把令牌搞清楚,很多问题都会瞬间通透。