Breaking Barriers and Assumptions: Techniques for Privilege Escalation on Windows: Part 1

July 30, 2024 | Michael DePlante and Nicholas Zubrisky

The number of link following vulnerabilities submitted to the Trend Micro ZDI program has been increasing rapidly over the past several years. These submissions have provided us with insight into how these vulnerabilities are being found and exploited.

In years prior we were seeing a lot of low-hanging fruit in this bug class where you simply just needed a privileged file operation in a directory with a permissive DACL. Now, as we will see with the vulnerabilities presented in this blog series, they often require programmatically exploiting a race condition. In a previous blog , we shared techniques that allow us to escalate our privileges using an arbitrary delete primitive, or to create a permanent DoS condition from an arbitrary file or folder creation primitive. In this blog series, we will discuss two additional techniques that take advantage of legacy functionality within Windows and provide various examples through the over 20 vulnerabilities that we found. We will also address some failures despite efforts and explanations from our side with various vendors. The techniques shared in this series were derived from submissions to the ZDI by Abdelhamid Naceri. 


What is a link following vulnerability?

A link following vulnerability, otherwise known as CWE-59, occurs when an application attempts to access a file by filename but doesn’t properly prevent that filename from resolving to an unintended resource. This means that if a malicious user has created an NTFS junction, hard link, or symlink, a vulnerable application will follow that link to a different location on the system. Since the default behavior in Windows is to transparently follow user-created links, most applications will follow them unless specifically instructed not to do so.

Scouting: The When, Where, and How

To look for this type of vulnerability, one must first find a target that is designed to run with elevated privileges and performs various file operations. After using Procmon to capture a log of file operations, the first thing we need to do is make sure the file operations are occurring in a location that a standard user has access to.

Next, we need to look for file operations that create, copy, move, rename, or delete files. Furthermore, any modifications to a file or folder’s Discretionary Access Control List (DACL) can be of interest. If any of these operations occur without checking for user-created links, we may have found a link following vulnerability that we can exploit! To successfully exploit a link following vulnerability, you must first be familiar with the file operations that can be used to create links. A good overview of the different types of useful file operations can be found here.

Eyes on the Defense

To prevent the CreateFile operation from following links, we observed several strategies employed by developers. As we will see, not all of these are sound practice.

1.         Using the FILE_FLAG_OPEN_REPARSE_POINT Flag: When opening a handle to a file, developers can use the FILE_FLAG_OPEN_REPARSE_POINT flag. NTFS uses reparse points, a collection of user-defined data attached to a file or directory, to implement linking behavior. This flag prevents the default behavior of following reparse points and instead opens the reparse point itself. Consequently, any subsequent operations performed using the returned handle will impact the reparse point rather than the file it points to.
2.         Checking for a Reparse Tag: Developers can check for a reparse tag after opening a handle to the file but before performing any operations using that handle. The NtQueryInformationFile function, used with the FileReparsePointInformation FileInformationClass value, can check a file or directory’s reparse tag. If the developer implements this, they must check for this every time they open a new handle.
3.         Using Protected and Hidden Files: Another method involves including a protected and sometimes hidden file in a directory with a restricted DACL. This will prevent an attacker from turning a directory into a mount point, as that attacker will already need elevated permissions to delete the protected file.
4.         Impersonation: We observed several instances of impersonation, which allows file operations to be executed within the security context of a specific user. An application impersonating a user can only access resources available to that user, even when following links.
5.         Redirection Guard: This more recently introduced mitigation was almost always disabled in the targets we looked at. In a future update to this blog, we will highlight the use of this and why we were able to get around it. For more details on Redirection Guard, you can check out this blog.

While this list is not a comprehensive guide to preventing link following vulnerabilities, our research identified these security measures that developers adopted to make exploitation more challenging for us.

New mitigations?

One technique that looked promising to us when we began our research was first discussed by James Forshaw in 2015. This technique results from an application improperly using impersonation. This allows a user to create a symlink in their device map that will be followed when the application impersonates that user and tries to open locations on a drive. As an example, if an application impersonates the current user and tries to open C:\Program Files, and the user creates a symlink at \??\C: pointing to C:\FakeRoot, the application will instead open C:\FakeRoot\Program Files.

We were introduced to this technique again by Abdelhamid Naceri. This researcher pointed out that when an application or driver running as NT AUTHORITY\SYSTEM wants to access a file on a network share, it must impersonate the user who mapped the share to do so. This gives us a convenient tool to force the products we are interested in to use impersonation. If the application fails to reverse this impersonation, file operations that usually occur in inaccessible locations can be redirected to occur elsewhere! However, once we started attempting to find some bugs using this technique, we quickly found out that the symlinks we created weren’t being followed.

It turns out that in September of 2023, Microsoft released a mitigation in response to ZDI-CAN-21597, among other reports within this bug class. This mitigation introduces the undocumented function ObpUseSystemDeviceMap, which will check if the file being looked up is on the system drive. If the file is on the system drive, and impersonation is being used, then the device map of the primary token will be used instead of the device map of the impersonated user. This explains why our symlinks are not followed as they only exist in the user’s device map.

However, there is a key word here that can potentially be taken advantage of. The mitigation only applies to file lookups that are on the system drive, which is usually “C:\”. This means that this technique can still be applied when the file operations occur on a non-system drive. These types of operations can be triggered when installing applications on non-system drives or by somehow triggering a file operation outside of the system drive. In our research, such operations were hard to come by and few products give you the option to change the install directory, however, this technique is still worth keeping in mind.

The following vulnerabilities from AVG and Avast demonstrate link following privilege escalation bugs with failed checks. The details of these cases are being released as zero-day vulnerabilities because the vendor has failed to coordinate with us and produce a patch. In a future update to this blog, we will provide another interesting case from an additional vendor. Please check back soon for that addendum.

ZDI-CAN-22803 and ZDI-CAN-22806: Avast Free Antivirus and AVG AntiVirus Free Link Following Denial-of-Service Vulnerability.

Most anti-virus solutions allow users to send files to and restore them from a quarantine. A quarantine is generally a location on the filesystem where the potentially malicious files can be stored so that they can’t impact the rest of the system. Files are usually sent to quarantine through a context menu and can be permanently deleted or restored through a UI. This is the case for both Avast Free Antivirus and AVG AntiVirus Free. Both products, despite being released by different vendors, are owned by the same parent company and share the majority of their code. The examples shown are from Avast Free Antivirus, but all the described behavior occurs in both programs.

The interesting behavior occurs when restoring a file from quarantine. First, we can create a file with the path “C:\1\2\TEST.exe” and using Avast’s UI we can send this file to the quarantine. Once the file is quarantined, we have the following options available to us:

By clicking Restore, Avast will perform some checks on the file’s parent directories before recreating it. This operation is interesting, but it’s not where we found this vulnerability. We started by asking ourselves the question, what if the file’s parent directories don’t exist? What happens when we delete “C:\1\2” before attempting to restore the file? If we do this and record a log of the file operations with Procmon, we can see the following happens:

The UI process, AvastUI.exe, will first check if the file already exists and then check if the file’s parent directory exists. Since they do not exist, indicated by the PATH NOT FOUND in Procmon, AvastSvc.exe will be triggered to recreate the non-existent directories. The directory creation operations are highlighted in blue, we can see that there is nothing done to prevent link following behavior. The FILE_FLAG_OPEN_REPARSE_POINT flag isn’t used, and there are no checks to see if there’s an existing reparse point before the directory is created. This AvastSvc.exe process also runs as NT AUTHORITY\SYSTEM, which means we can abuse this behavior to create an arbitrary directory!

An arbitrary directory creation isn’t going to let us do something as cool as escalating privileges, at least not without the created directory having a permissive DACL, but it is going to allow us to create a denial-of-service condition. If we create a directory or file at C:\Windows\System32\cng.sys, it will interrupt the machine’s boot process and prevent it from booting into Windows. We can get AvastSvc.exe to create an arbitrary directory by simply creating a junction at “C:\1” to the directory where we want our new directory to be created, in this case C:\Windows\System32. Instead of naming the child directory 2 as above, we will name it cng.sys. This is all that needs to be done to exploit this vulnerability! In this case we don’t need to worry about winning any races. We can just simply create a junction. For clarity, the exploit steps are as follows:

  1. Quarantine a file with the path “C:\1\cng.sys\TEST.exe”.
  2. Delete the path “C:\1\cng.sys”.
  3. Create a junction at “C:\1” pointing to “C:\Windows\System32”.
  4. Restore your quarantined file.

Following these steps, we can successfully exploit the vulnerability:

The first event highlighted in blue shows AvastSvc.exe successfully creating the directory C:\Windows\System32\cng.sys as intended. The second highlighted event shows a check for a reparse point, performed after the directory was created. While this check would have prevented our arbitrary directory creation, it was executed too late. However, this and similar checks do prevent our restored file from being placed in a location that would allow for privilege escalation.

ZDI-CAN-22960 and ZDI-CAN-22963: Avast Free Antivirus and AVG AntiVirus Free Link Following Local Privilege Escalation Vulnerability.

In the previous case we followed what happened when a file was restored from quarantine where the entire file path had to be reconstructed. Consider another edge case: What would happen if the process of recreating the path was interrupted? What if we were to create a directory in the path just before AvastSvc.exe was able to. What would happen then? Since the directories in the path are being created with the CREATE_NEW disposition, there might be some form of error handling if the directory creation fails. To test this behavior, we can once again quarantine a file, such as C:\1\2\3\TEST.exe, and delete the file path. Then we can restore this file while repeatedly trying to create the directory C:\1\2\3. If we manage to create C:\1\2\3 before AvastSvc.exe does, we see the following behavior recorded in Procmon:

The log shows that AvastSvc.exe successfully recreated C:\1 and C:\1\2. Our exploit then created C:\1\2\3 before AvastSvc.exe did, which caused AvastSvc.exe to run into a name collision when it also tries to create C:\1\2\3. This leads AvastSvc.exe to exhibit some interesting behavior. AvastSvc.exe will go back and attempt to delete every directory it had just created. If we can abuse this behavior to delete an arbitrary directory instead of the intended one, we can use this for privilege escalation. Once again, nothing is done to prevent following user created links when the directory is opened with delete access. The issue, however, is in the operations that occur beforehand:

In these operations, a check is performed on the directory before it is deleted. In this check, a handle to the directory is opened using the FILE_FLAG_OPEN_REPARSE_POINT, which is shown as Open Reparse Point in the disposition info in Procmon. Next, the service calls NtQueryInformationFile to retrieve the directory’s FileAttributeTagInformation, which can be used to determine if a directory or file is a reparse point. If this check fails, meaning that the check determines the directory is a reparse point, the service will not delete the directory and furthermore won’t attempt to delete any more directories.

However, this check is vulnerable to a Time-Of-Check-Time-Of-Use (TOCTOU) race condition. Since a different handle is used for the check and the delete, we can attempt to create our link after the check but before the delete. We must win this race in addition to winning the race to create C:\1\2\3 before AvastSvc.exe does. There’s no good way to do this other than brute force, but that is acceptable because there’s nothing stopping us from repeatedly quarantining and restoring a file until we win both races.

There is yet another challenge to address. To create our link at C:\1\2, this directory must be empty. This means that on top of creating a link at just the right time to pass the check, we also must delete C:\1\2\3 before we can create the link. That makes a lot of operations we need to perform in a short amount of time, so there are some things we should do to improve our chances of success.

The first few items are regarding how we create C:\1\2\3. When creating this directory using CreateFile, we can specify CREATE_NEW as the CreationDisposition value. This allows us to find out immediately if we have lost the race to create this directory before AvastSvc.exe does, since the last error value will be set to ERROR_ALREADY_EXISTS if AvastSvc.exe has created it first. This way we can immediately stop and retry the exploit. Another thing we can do when creating C:\1\2\3 is to set the flag FILE_FLAG_DELETE_ON_CLOSE. This flag lets us delete C:\1\2\3 more quickly, since all we need to do is close the handle we opened at creation time.

By changing which directory we use as a junction, we can give ourselves a further advantage. We will create our junction at C:\1 instead of C:\1\2. This gives our exploit more time to complete all its operations before AvastSvc.exe is ready to delete C:\1.

As a final optimization, we can use a separate thread to create the junction. The thread repeatedly attempts to create the junction in a tight loop running at high priority, and as soon as AvastSvc.exe deletes C:\1\2, the junction creation succeeds. The steps for this exploit can be summarized in the following flowchart:

Implementing these steps along with the above optimizations, we’re able to successfully cause AvastSvc.exe to follow our links and delete our target directory:

The Journey Continues

Link following is a great area for vulnerability research that offers plenty of opportunities for bug discovery. Tomorrow, we will release Part 2 of this series, where we will disclose more zero-days and detail a new technique that developers often do not sufficiently defend against. We found several vulnerabilities using this technique and were able to easily adapt our exploit to work on products from multiple AV vendors.

Until then, you can find us online at @Izobashi and @NZubrisky and follow the team on Twitter, Mastodon, LinkedIn, or Instagram for the latest in exploit techniques and security patches.