PowerSploit 权限提升指南:使用 Get-ModifiableScheduledTaskFile 排查可篡改的计划任务文件
【免费下载链接】PowerSploitPowerSploit - A PowerShell Post-Exploitation Framework项目地址: https://gitcode.com/gh_mirrors/po/PowerSploit
导读
Get-ModifiableScheduledTaskFile是 PowerSploit 权限提升模块(Privesc)中用于发现"计划任务可劫持点"的侦察函数:它会枚举 Windows 上全部计划任务的 XML 定义,解析出每个任务实际执行的命令与参数,再结合 NTFS ACL 判断当前用户是否对这些路径中的文件拥有写权限。读完本文,你将掌握该函数的完整用法、底层实现原理、输出结构,以及如何通过 Pester 测试验证其行为,为后续利用可写文件实现权限维持或提权打下基础。
一、函数定位:它在 PowerSploit 中扮演什么角色
Get-ModifiableScheduledTaskFile位于 Privesc/PowerUp.ps1,由 PowerUp 作者 Will Schroeder(@harmj0y)编写,采用 BSD 3-Clause 许可,其唯一必需的依赖是Get-ModifiablePath(同样定义于 Privesc/PowerUp.ps1)。
在 Privesc/Privesc.psd1 的模块导出清单中,该函数与Get-ModifiableRegistryAutoRun、Get-ModifiableServiceFile、Get-UnattendedInstallFile等一同被导出,共同构成 PowerUp 的"可修改持久化/自启动项"侦察家族。它与 Get-ModifiableServiceFile 思路一致:找到系统以高权限自动执行的程序路径,再检查该路径下是否有一个当前用户可以写入的文件——如果有,攻击者即可通过替换/修改该文件,让系统在下次触发时执行恶意代码。
从源码结构看,PowerUp 中所有Get-Modifiable*侦察函数输出的自定义对象都遵循PowerUp.Modifiable*命名,便于在Invoke-PrivescAudit中统一聚合展示(见 Privesc/PowerUp.ps1 附近对Get-ModifiableScheduledTaskFile的调用)。
二、功能概述与使用语法
SYNOPSIS 与 SYNTAX
该函数的功能一句话概括为:
返回所有"当前用户能够修改其关联文件"的计划任务。
函数不接受任何参数,也没有管道输入,语法极为简单:
Get-ModifiableScheduledTaskFile使用示例
Get-ModifiableScheduledTaskFile该命令会枚举系统中所有计划任务,并返回那些任务命令字符串中包含当前用户可修改文件的条目。
加载方式
PowerSploit 的 Privesc 模块清单 Privesc/Privesc.psd1 声明了PowerUp.ps1等文件,而 Privesc/Privesc.psm1 会通过 dot-source 加载模块目录下所有.ps1文件。因此可以通过以下任一方式使用:
# 方式一:导入整个 Privesc 模块 Import-Module Privesc # 方式二:直接点源加载 PowerUp.ps1 Import-Module .\Privesc\PowerUp.ps1 # 方式三:dot-source 整个 PowerUp 脚本 . .\Privesc\PowerUp.ps1三、工作原理:从任务 XML 到可写文件的完整链路
3.1 枚举计划任务定义
Windows 将每个计划任务以 XML 文件形式存放在%windir%\System32\Tasks目录下(该目录通常仅允许管理员与 SYSTEM 读取,因此使用此函数一般需要管理员权限,详见后文"测试与权限前提")。
源码首先拼接任务目录路径并递归枚举其中所有文件(跳过目录项):
$Path = "$($ENV:windir)\System32\Tasks" # recursively enumerate all schtask .xmls Get-ChildItem -Path $Path -Recurse | Where-Object { -not $_.PSIsContainer } | ForEach-Object {3.2 解析 XML 并提取命令触发串
对每个 XML 文件,源码将其内容强转为[xml]对象,并仅当任务定义了<Triggers>节点时才继续处理(没有触发器的任务不在考虑范围内):
$TaskName = $_.Name $TaskXML = [xml] (Get-Content $_.FullName) if ($TaskXML.Task.Triggers) { $TaskTrigger = $TaskXML.Task.Triggers.OuterXML注意$TaskTrigger保存的是整个<Triggers>节点的原始 XML 字符串,最终会随结果输出,方便判断该任务在什么时机(开机、登录、定时等)被触发。
3.3 分别检查 Command 与 Arguments 两个字段
任务动作(<Actions><Exec>)中与文件路径相关的字段有两个:Command(要执行的程序)和Arguments(命令行参数,参数中常常携带配置文件等路径)。源码对两者都执行Get-ModifiablePath过滤:
# check schtask command $TaskXML.Task.Actions.Exec.Command | Get-ModifiablePath | ForEach-Object { ... } # check schtask arguments $TaskXML.Task.Actions.Exec.Arguments | Get-ModifiablePath | ForEach-Object { ... }每个命中项都会被封装成一个PowerUp.ModifiableScheduledTaskFile类型对象(详见第四节)。
3.4 错误处理与执行上下文
- 函数将
$ErrorActionPreference临时改为SilentlyContinue,结束后恢复原值,避免枚举过程中的 ACL/权限错误刷屏; - 单个任务解析失败时进入
catch块,通过Write-Verbose "Error: $_"记录,不影响其他任务继续枚举。
四、输出结构:PowerUp.ModifiableScheduledTaskFile
函数声明了[OutputType('PowerUp.ModifiableScheduledTaskFile')],每个输出对象包含以下属性:
| 属性 | 类型 | 说明 |
|---|---|---|
TaskName | String | 计划任务名称(即 XML 文件名),同时通过Aliasproperty提供别名Name |
TaskFilePath | 嵌套对象 | Get-ModifiablePath返回的可修改路径对象(见下表) |
TaskTrigger | String | 任务触发器的原始 XML 片段,说明该任务的触发时机 |
其中TaskFilePath是Get-ModifiablePath的输出对象(类型PowerUp.ModifiablePath),嵌套包含:
| 属性 | 说明 |
|---|---|
ModifiablePath | 当前用户拥有修改权限的文件/目录绝对路径 |
IdentityReference | 拥有该权限的账户名或 SID |
Permissions | 命中修改条件的权限集合(如GenericWrite、WriteData/AddFile等) |
一个典型的输出形如:
TaskName : MyVulnTask TaskFilePath: @{ModifiablePath=C:\ProgramData\App\config.ini; IdentityReference=BUILTIN\Users; Permissions=System.Object[]} TaskTrigger : <Triggers><BootTrigger><Enabled>true</Enabled></BootTrigger></Triggers>五、底层支撑:Get-ModifiablePath 如何判定"可修改"
这是整个侦察链条的核心引擎。Get-ModifiablePath(Privesc/PowerUp.ps1)接收一段可能包含多个路径的字符串,通过以下步骤找出当前用户可写的文件:
5.1 路径 tokenization(分词)
由于任务参数往往是"vuln.exe" -i "C:\Temp\config.ini"这类混合字符串,函数会用一组分隔符反复拆分目标串,生成候选路径集合:
$SeparationCharacterSets = @('"', "'", ' ', "`"'", '" ', "' ", "`"' ")拆分出的每个 token 先经ExpandEnvironmentVariables展开环境变量(如%SystemRoot%),再Test-Path判断:
- 路径存在→
Resolve-Path解析为绝对路径加入候选; - 路径不存在→ 尝试取父目录加入候选(因为文件尚不存在时,其父目录是否可写同样意味着可在此处创建/替换文件)。
5.2 ACL 权限比对
对每个候选路径调用Get-Acl,取其允许(AccessControlType -match 'Allow')的访问控制项,并用一张权限掩码表($AccessMask,覆盖GenericRead、GenericWrite、GenericAll、WriteOwner、WriteDAC、WriteData/AddFile、AppendData/AddSubdirectory等)将原始权限位翻译为可读名称。
5.3 可写判定与身份匹配
可写判定的核心是如下对比:
$Comparison = Compare-Object -ReferenceObject $Permissions -DifferenceObject @( 'GenericWrite', 'GenericAll', 'MaximumAllowed', 'WriteOwner', 'WriteDAC', 'WriteData/AddFile', 'AppendData/AddSubdirectory') -IncludeEqual -ExcludeDifferent即:只要当前权限集中包含上述任一"可写类"权限即视为命中。随后,将 ACE 的IdentityReference(必要时从账户名翻译为 SID)与当前用户及其所属组的全部 SID 集合比对($CurrentUserSids由WindowsIdentity.GetCurrent()的User.Value与Groups合并而来),只有权限授予了当前用户本人或其所在组,才会作为结果输出。这一步保证了"我能改"这一结论与身份真实匹配,而非只看 ACL 上存在宽泛授权。
六、滥用思路:从"可写文件"到"代码执行"
Get-ModifiableScheduledTaskFile只负责侦察,不包含任何利用逻辑。但结合其输出,经典的利用链路是:
- 找到某计划任务以高权限(如
SYSTEM)运行,且其Command或Arguments指向的文件当前用户可写; - 在该文件原位置写入恶意载荷(替换原文件,或在父目录可写的情况下放置同名文件);
- 等待任务触发(
TaskTrigger字段正好揭示了触发时机),或以管理员身份手动触发任务,恶意代码即以任务上下文执行。
需要注意:在未验证的情况下,本函数发现的可写文件能否实际造成代码执行,取决于任务以何种权限运行、文件是否真的会被加载执行等因素,实际利用前仍需人工确认任务配置。
七、测试验证:Pester 用例如何印证函数行为
Tests/Privesc.tests.ps1 中为Get-ModifiableScheduledTaskFile编写了专门的 Pester 测试,是理解其行为的最佳实证:
Describe 'Get-ModifiableScheduledTaskFile' { if (-not $(Test-IsAdmin)) { Throw "'Get-ModifiableScheduledTaskFile' Pester test needs local administrator privileges." } It 'Should not throw.' { {Get-ModifiableScheduledTaskFile} | Should Not Throw } It 'Should find a vulnerable config file for a binary specified in a schtask.' { try { $FilePath = "$(Get-Location)\$([IO.Path]::GetRandomFileName())" $Null | Out-File -FilePath $FilePath -Force $Null = schtasks.exe /create /tn PowerUp /tr "vuln.exe -i '$FilePath'" /sc onstart /ru System /f $Output = Get-ModifiableScheduledTaskFile | Where-Object {$_.TaskName -eq 'PowerUp'} | Select-Object -First 1 $Null = schtasks.exe /delete /tn PowerUp /f $Output.TaskFilePath.ModifiablePath | Should Be $FilePath ...这段测试还原了真实攻击场景:先用schtasks.exe /create创建一个名为PowerUp的计划任务,其命令串为vuln.exe -i '<随机文件路径>'(/sc onstart开机触发、/ru System以 SYSTEM 运行),随后调用函数应能精确命中该任务,且TaskFilePath.ModifiablePath指向那个可控文件。测试同时断言了TaskName、TaskFilePath、TaskTrigger以及嵌套的ModifiablePath、Permissions、IdentityReference字段全部存在,验证了输出结构的稳定性。
两点值得注意的权限前提:
- 该测试要求本地管理员权限(
Test-IsAdmin守卫),这既因为需要读取System32\Tasks目录与创建/删除计划任务,也提示了函数本身在高权限环境下信息收集更完整; - 测试用例把随机文件直接创建在当前目录,正好演示了"参数中携带的路径是攻击者可写位置"这一判断是如何被识别的。
八、适用场景与注意事项
适用场景
- 权限提升侦察:在横向/提权前期,快速盘点是否存在"高权限计划任务引用低权限可写文件"的可利用点;
- 权限维持评估:检查自己创建或系统既有的计划任务是否存在被其他低权限用户篡改的风险;
- 安全审计:批量核查域内/本机计划任务的文件引用 ACL 是否合规。
注意事项
- 该函数无参数、无管道输入,直接运行即可;
- 输出基于 ACL 静态判断,无法保证"可写文件"一定能被任务实际加载执行,需结合
TaskTrigger与任务实际动作人工研判; - 函数依赖
Get-ModifiablePath,两者同属 Privesc/PowerUp.ps1,单独抽取脚本时需一并加载; - 对
%windir%\System32\Tasks的递归读取通常需要管理员权限,非管理员环境下结果可能不完整; - 该函数属于侦察类功能,本身不修改任何系统状态(仅读取任务 XML 与文件 ACL),可安全在授权评估环境中运行。
九、延伸阅读
- PowerUp 权限提升审计总览:了解本函数在完整提权侦察流程中的位置;
- Get-ModifiableServiceFile:同一侦察思路在 Windows 服务场景的对应实现;
- Get-ModifiableRegistryAutoRun:检查注册表自启动项中的可写文件;
- Invoke-PrivescAudit:一键聚合运行全部 PowerUp 检查,其中包含本函数;
- 模块导出清单与加载方式参见 Privesc/Privesc.psd1 与 Privesc/Privesc.psm1。
【免费下载链接】PowerSploitPowerSploit - A PowerShell Post-Exploitation Framework项目地址: https://gitcode.com/gh_mirrors/po/PowerSploit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考