This post will explain how to find privilege escalation vuls on Windows that no one appears to be looking for, because it's been pretty easy to find a bunch of them. After explaining how to find them, I'll introduce some defenses that can partly mitigate the problem in different ways. But what I'd like to see change is for developers to start looking for these vuls in the way I describe so that they stop introducing them in the first place.
Back when we first released CERT BFF, the usual process for putting together a proof-of-concept exploit for a memory corruption vulnerability was:
- Fuzz the target until you get control of the instruction pointer.
- Find out which bytes can be used to store your shellcode, using BFF string minimization.
- Use ROP as necessary to modify the program flow so that it executes your shellcode.
It was often relatively straightforward to go from Start to PoC with CERT BFF. As time went on, the bar for exploiting memory corruption vulnerabilities was raised. This can likely be attributed to two things that happened over the years:
- Increased fuzzing by parties releasing software.
- Increased presence of exploit mitigations in both software and the platforms that they run on.
I have recently worked on a vulnerability discovery technique that reminded me of the early BFF days. Both with respect to how easy it is to find the vulnerabilities and also how easy it can be to exploit them. In fact, the concept is so trivial that I was surprised by how successful it was in finding vulnerabilities. Just like the idea of going directly from fuzzing with BFF to a working exploit became less and less viable as time went on, I'd like for there to be much less low-hanging fruit that can be easily found with this technique.
In this post I will share some of my findings as well as the filter itself for finding privilege escalation vulnerabilities with Sysinternals Process Monitor (Procmon).
When a program is installed on the Windows platform, some components of it may run with privileges, regardless of which user is currently logged on to the system. These privileged components generally take two forms:
- Installed services
- Scheduled tasks
How might we achieve privilege escalation on a Windows system? Any time that a privileged process interacts with a resource that an unprivileged user may be able to influence, this opens up the possibility for a privilege escalation vulnerability.
What to look for
The easiest way to check for privileged processes that might be able to be influenced by non-privileged users is to use a Process Monitor filter that displays operations based on the following attributes:
- Files or directories that do not exist.
- Processes that have elevated privileges.
- Locations that may be writable by an unprivileged user.
Checks 1 and 2 can be trivially implemented in Process Monitor. Check 3 is a little more complicated and may result in some false positives if we limit our tool to strictly what can be done with a Process Monitor Filter. But I've created a filter that seems to do a pretty good job of making privilege escalation vulnerabilities pretty obvious.
Using the filter
Using the Privesc.PMF Process Monitor filter is relatively straightforward:
- Enable Process Monitor boot logging (Options → Enable Boot Logging)
- Reboot and log in
- Run Process Monitor
- Save the boot log when prompted
- Import the "Privesc" filter (Filter → Organize Filters → Import...)
- Apply the Privesc filter (Filter → Load Filter → Privesc)
- Look for and investigate unexpected file accesses.
Let's start by looking at a boot log of a common baseline that we might deal with as a vulnerability analyst - a 64-bit Windows 10 2004 system with VMware Tools installed:
Even with virtually no software installed in our VM, we can already see something suspicious: C:\Program%20Files\
Windows users may be familiar with the path C:\Program Files\, but what's with the %20? Why might such a file operation occur? We'll cover the reason in the section below.
Mistakes that developers make
There are a number of mistakes that a developer might make that can lead to a privileged process being able to be influenced by an unprivileged user. The mistakes that I've noticed with respect to simple privilege escalation vulnerabilities with Windows applications fall into two main categories:
- Unexpected paths being accessed.
- Unexpected Access Control Lists (ACLs) applied to paths being used.
Unexpected paths being accessed
In some cases, an unexpected path is accessed during the execution of a program. That is, the developer would probably be surprised if they realized that the path was being accessed. These unexpected path accesses can be caused by a number of reasons:
As we noticed in the screenshot above, the VMware Tools process VGAuthService.exe attempts to access the path C:\Program%20Files\VMware\VMware%20Tools\VMware%20VGAuth\schemas\xmldsig-core-schema.xsd. How might this happen? If a path containing spaces is URL-encoded, those spaces will be replaced with %20.
What are the consequences of this transformation? The most important aspect of this new path is that rather than being a subdirectory of C:\Program Files\, which has proper ACLs by default, this requested path now starts looking at the root directory. Unprivileged users on Windows systems can create subdirectories off of the system root directory. This will be a recurring theme, so remember this.
From an unprivileged command prompt, let's see what we can do:
We can dig a little deeper in Process Explorer by selecting the file access and pressing Ctrl-K to get the call stack:
Here we can see that the file access is triggered by VGAuthService.exe + 0x110d9, and along the way there is a call to xmlLoadExternalEntity().
Putting all of the pieces together here, we have a privileged process that attempts to load a file that does not exist because the path is URL encoded. Since an unprivileged user can create this path, this now turns into a case where an unprivileged user can influence a privileged process. In this particular case, the consequences are only an XML External Entity (XXE) vulnerability. But we're also just getting warmed up.
If an application uses a POSIX-style path on a Windows machine, this path is normalized to a Windows style path. For example, if a Windows application attempts to access the /usr/local/ directory, the path will be interpreted as C:\usr\local\. And as described above, this is a path that an unprivileged user can create on Windows.
Here is a Process Monitor log of a system with a fully-patched security product installed:
Using a publicly-known technique for achieving code execution via openssl.cnf, we can now demonstrate code execution via running calc.exe with SYSTEM privileges from a limited user account:
Use of a library that loads from an unexpected path
In some cases, a developer may have done nothing wrong other than using a library that happens to have load from a location that can be influenced by an unprivileged Windows user. For example, here's a Process Monitor log of an application that attempts to access the path C:\CMU\bin\sasl2:
If we look at the call stack, we can see that this access is likely triggered by the libsasl.dll library:
And sure enough, if we look at the code for libsasl, we can see a hard-coded reference to the path C:\CMU\bin\sasl2.
As an unprivileged user, we can create the directory and place whatever code we want there. Once again, we have calc.exe executing with SYSTEM privileges. All from an unprivileged user account.
Use of paths that only exist on a developer's system
Sometimes a program may contain references to paths that only exist on the developer's system. As long as the software functions properly on systems that do not have such a directory, then this attribute may not be recognized unless somebody is looking. For example, this software looks for a plugins subdirectory in the C:\Qt\ directory:
I'll skip some steps for the sake of brevity, but after a bit of investigation we see that we can achieve code execution by placing a special library in the appropriate directory:
Looking further into the Qt development platform, this type of vulnerability is a known issue. The vulnerability was patched more than 5 years ago, but it never received a CVE. Software may be vulnerable to privilege escalation if it was built with a Qt version from before this patch was introduced or the developer did not use windeployqt to patch out the qt_prfxpath value stored in Qt5core.dll.
Unexpected ACLs applied to paths being used
Most cases of an unexpected path being accessed by an application can be exploited because of a simple fact: unprivileged users can create subdirectories off of the Windows system root directory. Finding and exploiting software that fails to properly set ACLs requires just a bit more investigation.
Most of the ACL issues related to Windows software is related to one concept:
Software that executes from a subdirectory of C:\Program Files\ or C:\Program Files (x86)\ has secure ACLs by default by virtue of inheritance. For example, consider the case where I install my software to C:\Program Files\WD\. Unprivileged users will not be able to modify the contents of the WD subdirectory because its parent directory of C:\Program Files\ cannot be written to by unprivileged processes, and the WD subdirectory by default will inherit its parents permissions.
Using the C:\ProgramData\ directory without explicitly settings ACLs
The ProgramData directory by design can be written to without elevated permissions. As such, any subdirectory that has been created in the ProgramData directory will by default be writable by unprivileged users. Depending on how an application uses its ProgramData subdirectory, a privilege escalation may be possible if the ACLs for the subdirectory are not explicitly set.
Here we have a popular application that has a scheduled update component that runs from the C:\ProgramData\ directory:
This is a straightforward potential case of DLL hijacking, which is made possible due to lax ACLs on the directory from which the software runs. Let's plant a crafted msi.dll there and see what we can accomplish:
There's our calc.exe, executing with SYSTEM privileges. These problems seem a bit too prevalent. And easy to exploit.
It's worth noting that DLL hijacking isn't our only option for privilege escalation. Any user-writable file that is used by a privileged process introduces the possibility of introducing a privilege escalation vulnerability. For example, here's a popular program that checks for a user-creatable text file to direct its privileged auto-update mechanism. As we can see here, the presence of a crafted text file can lead to arbitrary command execution. In our case, we have it launch calc.exe:
Installing to a subdirectory off of the system root
An installer that places an application by default to a directory off of the system root must set appropriate ACLs to remain secure. For example, Python 2.7 installs to C:\python27\ by default:
The default ACLs for this directory allow unprivileged users to modify the contents of this directory. What might we be able to do with this? We can try the standard DLL hijacking technique:
But we don't even need to be that clever. We can simply replace any file in the C:\python27\ directory as an unprivileged user:
Allowing user-specified installation directories without settings ACLs
Many installers are secure because of inheritance of secure ACLs from C:\Program Files\. However any installer that allows a user to choose their own installation directory must explicitly set ACLs in the target location. Sadly, in my testing I've found that it is very rare for an installer to explicitly set ACLs. Let's take a look at the Microsoft SQL Server 2019 installer, for example:
Does the installer set ACLs to the directory where it installs the software?
What happens when SQL Server 2019 starts?
Microsoft SQL Server 2019, as well as just about any Windows application that allows you to choose where to install it, might be vulnerable to privilege escalation simply based on what directory it is installed to.
Defenses against privilege escalation
Remove "Create folders" permission on system root for unprivileged users
The simplest defense against many of the attacks outlined above is to remove the permission to create folders off of the system root directory:
Do not install software outside of C:\Program Files\
If software is installed to any location other than C:\Program Files\ or C:\Program Files (x86)\, you are relying on the installer to explicitly set ACLs for it to be secure. You can avoid needing to make this leap of faith by only installing software to recommended program locations.
Test and fortify your own systems
You can test your own platforms for privilege escalation vulnerabilities using the Process Monitor filter and techniques described above. For any file locations that are determined to be insecure, you can manually lock down those directories so that unprivileged users cannot modify those locations. For any vulnerabilities that you discover, we recommend contacting the affected vendors to notify them of the vulnerabilities so that they can be fixed for everyone. In cases where the vendor communications are unproductive, the CERT/CC may be able to provide assistance.