CVE-2020-16939: Windows Group Policy DACL Overwrite Privilege Escalation
October 27, 2020 | Guest BloggerIn 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:
The output of the gpupdate /target:user /force
command looks something as follows:
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.
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.
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):
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.
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?
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:
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.
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.
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.
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.
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.
- Extract the PoC to a location on a local hard disk which is writable by a normal user.
- 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.