Earlier this month, Microsoft released an advisory for CVE-2020-1317 which describes a privilege escalation vulnerability in Group Policy. This was further detailed by the discoverer of the vulnerability on the Cyberark website. The nature of this issue is interesting and worth understanding. For years, Group Policy has had this dichotomy built into it’s design. Namely, the need to deliver per-user policy to otherwise unprivileged users, through a mechanism that runs as a privileged process–namely the Group Policy engine. Microsoft has handled this in a variety of ways. For example, per-user Administrative Templates policy was delivered to a set of two registry keys in HKEY_CURRENT_USER that happened to be permissioned such that a user could not write to them, even though they could write to pretty much any other key in this hive. But in the case of this CVE, there were a couple of glaring holes. The first of these was detailed in the Cyberark blog post. Namely, GP Preferences has always had the concept of history files. These files were created to essentially undo the state of certain GP Preferences when those settings no longer applied. These history files were essentially copies of the actual settings file that was stored in SYSVOL for the GPO delivering the settings, and were stored in the user’s profile under %appdata%\Local\Microsoft\Group Policy, as you can see here:
The location of this file was the key to this privesc vulnerability. the files themselves are stored in the user’s profile in an area that a regular, non-privileged user has full rights to. However, the files are written here whenever a new GPP setting is processed, or the setting is changed, by the GP engine, which itself runs as localSystem. So, we have a localSystem process writing data to unprivileged user space, and this is where the fun begins.
For this particular exploit, the author used Object Manager symbolic links to redirect any arbitrary GPP XML file to a location in c:\window\system32, where it can be exploited by a further process to essentially open a process running as localSystem. The details of the actual escalation code to localSystem are not shown, but the use of OM symbolic links (and the exploitation of other types of Windows symbolic links), is described in this excellent video from James Forshaw, who also has sample code for creating these links here. Suffice it to say that to exploit this, an attacker has only to delete the file that GPP left in their user profile, and replace it with an OM symbolic link. The GP Engine will then come along, happily running as local system, and essentially write a new XML file to whatever the destination that is specified in c:\windows\system32. They can then write whatever content they want to that file and exploit it from there. Pretty slick.
Alternative Exploit Paths & the Patch
When I first saw this exploit described, I wondered out loud if GPP History files were the only place where this could be exploited. I knew for a fact that my good friend, the registry archive file (ntuser.pol), was another one of these per-user GPO files that were held in the user’s profile, and writeable by the user, but updated by the GP Engine. So it was logical to assume that this file could also be exploited in the same way as GP Preferences history files. So when I look at a Windows system after applying the patch for this vulnerability, I noted some interesting things. First, the way Microsoft solved this, was to move GPP history files out of the user profile, and into the protected %programdata% folder (specifically under %programdata%\Microsoft\Group Policy\Users). This folder is not writeable by normal users, which helps to avoid the use of symbolic links in the previous version. The full path to the file is a bit wonky, as you can see here:
The folder structure is per-user (i.e. the SID folder path), then per GPO (the GPO GUID), then per-user again (another SID folder), which seems a bit excessive, but I’m sure they had a reason for it 🙂
The next question I was curious about, was the ntuser.pol file I mentioned above. Sure enough, Microsoft also moved that file to a location in %programdata% as well, albeit in a different folder structure from GPP history files. Namely, under %programdata%\Microsoft\GroupPolicy\Users\<SID of User>. I thought it was mildly funny that they have two folder structures under the Microsoft folder, one called “Group Policy” and the other called “GroupPolicy”. Oh well. This latter path is also where the per-user Group Policy Cache files are kept. See this article for a review of the Group Policy cache.
Now, according to the author of this exploit, it took Microsoft nearly a year to fix this. I can absolutely imagine this being the case. Given that, between all the GP Preferences areas, and, all the areas that use ntuser.pol, including Admin Templates, AppLocker, Windows Firewall, Public Key Policy and probably a few I’m missing, that could have been potentially a lot of code to go through to make sure moving those files didn’t break everything. I wouldn’t be surprised if some breakage does still show up in some obscure corner of policy before this is all over. Nevertheless, this was an interesting exploit, made more interesting by some 20 year old warts that GP brought to the party.
Thanks Darren for that great article, that gives much more information than Microsoft provided along with this “fix”. You said, “I wouldn’t be surprised if some breakage does still show up in some obscure corner of policy before this is all over”.
And you were right.
There are scenarios where group policy processing is no longer happening as expected.
These scenarios are all about “removing user settings”, e.g. by unlinking a GPO or any other reason for getting a GPO out of a user’s scope.
Usually, when a GPO no longer applies, all the true policies are removed again.
But since CVE-2020-1317 the ntuser.pol is maintained and expected in a new path (as explained by Darren in the blog) and it is no longer part of the user profile. But the actual policy registry settings (stored in ntuser.dat) are still there. So, what can happen is this:
– patch is being installed
– for some time, no change might happen for the registry CSE, so no ntuser.pol is created for the user in that new path underneath %ProgramData%
– then a GPO (with user settings!) is unlinked or maybe a setting is removed from an already linked GPO (or group filtering makes a GPO to no longer apply, or same for a WMI filter)
– that will finally trigger a GPO refresh for the user with re-applying the registry settings
– but there is no ntuser.pol, so none of the previously applied settings will be undone
– as a consequence, only the current valid settings will be written to the registry (and now also we have the ntuser.pol in the new location)
– but the “removed” settings stay tattooed in the user registry hive (ntuser.dat): nothing will remove them, because from the perspective of the GPO Engine they were not added via GPO.
In short:
thanks to this new design, there can be unexpected “ghost” settings in your user profiles.
And when you have Roaming Profiles and users changing the clients to log on, there can be the same symptom, caused by policies settings being replicated via the user profile, but the rollback information not being present in the user profile.
Ugh! thanks for sharing this Patrick. Not good at all, but totally expected now that you’ve laid out the scenario.
Darren
So, how do we deal with that, assuming we can track down the cause of the misbehavior?
There’s nothing easy that I know of. Frankly, you could try re-copying ntuser.pol from the new location back to the old location for that first GP refresh, but that’s a major hack.
One way to lower the impact: Make sure in your domain the next GPO change that happens after that patch has been installed, is something “additive”, and not something which removes a setting.
Once that change has happened, the ntuser.pol for the user is created in the right location and can again be used for “undoing” policies.
So it is a matter of timing – and there are only ways to mitigate the situation.
But I doubt there is a 100% solution.
And if MS will not fix the fix, certain scenarios will stay potentially affected permanently (e.g. Roaming User Profiles and changing clients).