CVE-2020-16939: Windows Group Policy DACL Overwrite Privilege Escalation

October 27, 2020 | Guest Blogger

In October, Microsoft released a patch to correct a vulnerability in the Windows Group Policy client. The bug could allow an attacker to execute code with escalated privileges. This vulnerability was reported to the ZDI program by security researcher Nabeel Ahmed. He has graciously provided this detailed write-up and Proof-of-Concept detailing ZDI-20-1254/CVE-2020-16939.


This vulnerability abuses a SetSecurityFile operation performed during Group Policy update that is done in the context of NT AUTHORITY\SYSTEM. This operation is performed on all files within a certain folder. An attacker could create a directory junction to another folder and thereby obtain full permissions on the contents of that folder.

This vulnerability is similar to CVE-2019-0841 and CVE-2020-1317 as the end result is the same, except this one is triggered by Group Policy updates.

Intro

Group Policy Caching has been in use since Windows 8.1. It keeps a copy of the group policies in a local cache for performance purposes. User GPO Settings are stored in %programdata%\Microsoft\GroupPolicy\Users, while Computer GPO Settings are stored in %windir%\System32\GroupPolicy\DataStore.

The Vulnerability

As mentioned before, when a group policy update occurs, the policies are cached locally. We are particularly interested in the User GPO Settings cache location, which is %programdata%\Microsoft\GroupPolicy\Users. This is interesting because %programdata% is writeable by default even by a low-privileged user.

We first look at what file operations look like when we launch the gpupdate command in Windows as a low-privileged user. We will use Process Monitor from Sysinternals to get visibility into the various file operations.

We will use the ProcMon filter and highlight settings shown in the following screenshots:

Figure 1 - Process Monitor Filter settings

Figure 2 - Process Monitor Highlighting settings

The output of the gpupdate /target:user /force command looks something as follows:

Figure 3 - Procmon output Group Policy Update user GPO

When we scroll down the list of operations and skip the parts that are related to process creation, we soon start seeing SetSecurityFile operations that are remarkably performed without impersonation (lacking highlighting). That could be significant if the written Discretionary Access Control List (DACL) is too permissive.

Figure 4 - "SetSecurityFile" operations identified

As we will see later, some of the DACLs written are permissive, granting full control to the attacker. However, we still don’t have an exploitable condition because files in these locations are not useful for conducting a privilege escalation. To try to redirect these DACL writes so that they apply to files that are useful to us, we can try to place a directory junction within the folder hierarchy. This may cause the Group Policy Update process to open our chosen target folder instead and write a DACL there.

Looking at the permissions on the folders, we notice the first obstacle: if a group policy update has already occurred, you’ll notice that under the %programdata%\Microsoft\GroupPolicy\Users directory, an additional directory is created for each domain user based on his SID. The permissions on this folder do not allow us to write. The owner of the folder is the Administrators group and the Users group has only read and execute permissions, as inherited from the %programdata%\Microsoft folder.

Figure 5

However, if we move down the folder structure and analyze each folder then you will notice one small difference. The sysvol folder under the %programdata%\Microsoft\GroupPolicy\Users\<SID>\Datastore\0\ folder has the low-privileged user as the owner of the folder (seen in the screenshot below):

Figure 6

Since the low-privileged user is the owner of the sysvol folder, they can alter permissions on that folder without much issue. To do this, the low-privileged user should disable inheritance, and afterwards grant themselves "Full Control".

Once done, the low-privileged user is able to write files under the sysvol directory, and every folder and file under this folder is subjected to a DACL write operation in the context of NT AUTHORITY\SYSTEM when a Group Policy update is run.

Now this is starting to become more interesting. By creating a new folder within sysvol that is a junction to a chosen location, we can control which files and folders the Group Policy Service will open. In the following experiment, we told it to go to C:\Program Files (x86)\Microsoft, and it obediently followed the Directory Junction put in place by a low privileged user.

Figure 7 - Directory Junctions (Reparse Points) are followed by Group Policy Service

However, controlling the flow by putting a directory junction doesn’t necessarily mean a low-privileged user can abuse it for anything useful. The useful part we’re interested in is the DACL write operation. Does the write operation occur after being “redirected” by a directory junction?

Figure 8

In the screenshot above you can see that the DACL permissions are indeed successfully overwritten.

The next question is whether the written DACL is sufficiently permissive to provide the attacker with a path to privilege escalation. Alas, no:

Figure 9 - DACL Permissions before

Figure 10 - DACL Permissions after Group Policy update and directory junction reparse

After successfully reparsing to several locations, I noticed that when the DACL write process is interrupted abruptly due to the SYSTEM user not having sufficient privileges or due to the fact that the file in question was in use, the permissions written actually granted the low-privileged user Full Control permissions.

Figure 11 - Access error caused DACL to write process to be stopped

One method to force an error is by deleting the junction point before it completes writing the DACL operation. The correct moment to delete the junction point can be detected by using an opportunistic lock (oplock).

Exploit

We can use this behavior to set file permissions on folders/files (where SYSTEM has full control rights or where SYSTEM is the owner of the file) by using junction points and oplocks.

Figure 12 - Low privileged user has insufficient privileges on VMware Tools folder

As you can see, we are currently logged in as a regular user with no administrative privileges. In this example, we will try to take control of the VMware Tools file located in C:\Program Files\VMware. Currently, we only have Read/Execute permissions on the folders and the files within.

We create a junction point within the Group Policy cache folder sysvol pointing to C:\Program Files\VMware. The name of junction point should start with $. We also create a second folder with a random file inside. On that random file, we set an OpLock so we can cause an error.

Once the OpLock is triggered, we delete the junction point and release the OpLock. As the DACL write worked only partially and the junction point is deleted, the Full Control permissions are retained. After successful exploitation, the current user has full permission on the target folder and the files within. The user is now able to modify the contents of the file, which results in an escalation of privilege.

Figure 13 – Low-privileged user has now full permissions on the “VMware Tools” folder

Below you will find a second example where a low-privileged user attempts to hijack files within C:\Windows\System32\config. In this case, the OpLock never triggers, because the operation is halted by a previous file where the DACL write operation fails. However, part of the content within the config folder has the new DACL, including the SAM file.

Figure 14 - Low privileged user has now full permissions on the “SAM” file

Figure 14 - Low privileged user has now full permissions on the “SAM” file

Proof of Concept

I’ve provided a PoC (https://github.com/thezdi/PoC/tree/master/CVE-2020-16939) which automates the hijack. It should be executed as a low-privileged user without administrative rights.

  1. Extract the PoC to a location on a local hard disk which is writable by a normal user.
  2. Execute the PoC executable file passing one argument, specifying the path to the folder to which you want the junction point to redirect to. For example: FolderTakeover.exe C:\Windows\System32\Tasks

Thanks again to Nabeel for providing this great write-up and PoC. He has contributed several bugs to the ZDI program over the last couple of years, and we certainly hope to see more submissions from him in the future. Until then, follow the team for the latest in exploit techniques and security patches.