Monday 28 February 2022

CVE-2021-42287/CVE-2021-42278 Weaponisation

On the 9th November 2021, Cliff Fisher tweeted about a number of CVE's related to Active Directory that generated a lot of attention. These included CVE-2021-42278, CVE-2021-42291, CVE-2021-42287 and CVE-2021-42282. The one that stood out to me the most was CVE-2021-42287 as it related to PAC confusion and impersonation of domain controllers, also having just worked on PAC forging with Rubeus 2.0.

This post discusses my quest to understand how to exploit this issue and the findings I discovered along the way.

I want to highlight that there is no new research in this post, this issue was discovered by Andrew Bartlett of Catalyst IT. I however, found one way to weaponise it, there may well be other methods in the issues he found. 

 

A Little Digging

Immediately upon seeing Cliff's tweet, Ceri Coburn and I started trying to investigate how this could be exploited. We (perhaps incorrectly) latched onto the text on Microsoft’s description of CVE-2021-42287 which seemed to be based around the idea of TGT's being issued without PACs. This led me to modify Rubeus to allow for requesting TGT's without a PAC.

After Ceri debugged the Windows KDC and we had dug through the leaked XP source we were convinced that to trigger the code path we needed to insert a PAC into a ST when it was requested with a TGT lacking a PAC. This required a cross domain S4U2self but we were unable to get it to work. The only way we could get a DC to add a PAC when a service ticket (ST) was requested using a TGT without a PAC was by configuring altSecurityIdentities.

This process involves modifying the altSecurityIdentities attribute of an account in a foreign domain to Kerberos:[samaccountname]@[domain] to impersonate that user.

In the below screenshot you can see a low privileged user (internal.user) of the local domain (internal.zeroday.lab) has GenericAll over a high privileged user (external.admin) of a different domain (external.zeroday.lab): 

As this user we can add ourselves to the altSecurityIdentities attribute as shown in the following screenshot:

Now we can get a TGT from our local DC and request it without a PAC using the new /nopac switch:


This results in an obviously small TGT. We then use that TGT to request a referral to the target domain (external.zeroday.lab) from our local DC: 


That referral can then be used to request ST's for services on our target domain (external.zeroday.lab). Here I'm requesting a ST for LDAP/EDC1.external.zeroday.lab the DC's LDAP service: 


The size of the ST is very large compared to the previous 2 tickets; this is because (as we'll see) a PAC has been added. As shown in the klist output below, this ST is for the original user internal.user who has no special privileges on either domain: 


Using this ST, however, we can DCSync:


Here the DC has searched for the account in the local database, it hasn't found it, so it has then searched to see if any accounts have this account listed in their AltSecurityIdentities attribute, which external.admin does because we added it earlier, and if so, the DC adds a PAC belonging to that account. This can be verified using the Rubeus' describe command and the AES256 key we just DCsync'd:


 We now effectively have the privileges of the external.admin user on the external.zeroday.lab domain.

This did not help us exploiting the issue we wanted but I did find it interesting. 

 

Some Progress

Clément Notin then posted this tweet which actually mentioned the Samba information regarding these issues and led me to CVE-2020-25719 and this patch. What particularly caught my attention was this paragraph:

“Delegated administrators with the right to create other user or machine accounts can abuse the race between the time of ticket issue and the time of presentation (back to the AD DC) to impersonate a different account, including a highly privileged account.”

I realised to make the local lookup fail, we didn't need to attack a foreign domain but perhaps remove the account after retrieving the TGT.

I started playing with naming a machine account the same as the local DC (minus the $), requesting a TGT (still without a PAC) and removing the machine account and using that TGT. I noticed something unusual.

When using this PAC-less TGT with a U2U request but without supplying an additional ticket, it failed to decrypt the resulting ST: 


The U2U ST should be encrypted with the session within the provided TGT but as I did not provide an additional ticket I assumed it was trying to lookup the account based on the sname which I was setting to IDC1 the samaccountname of my now missing machine account. I tried decrypting this ST using the long-term key of the domain controller that I had named my machine account after (IDC1$):


 It successfully decrypted the ST, it just couldn't find the PAC because there wasn't one there. I tried the same thing using S4U2self and got the same result, the DC was looking for my IDC1 account, not finding it and then searching for the same but adding a $ on the end, finding the domain controller account and encrypting the ticket using its key instead.

At that time, I still couldn't figure out why it wasn't adding the PAC, so I decided to request the initial TGT with a PAC instead of without a PAC and it was successful. There was no need to request a TGT without a PAC, supplying a TGT with a PAC for an account that has the samaccountname of the DC minus the $ to a request for an S4U2self ticket, when the initial account no longer exists, results in the ST being encrypted using the key to the DC.

The sname of that resulting ST can be modified as per Alberto Solino's post here. It can be used to authenticate against any service on the target box, even users protected from delegation, as Elad Shamir mentions in the Solving a Sensitive Problem section of Wagging the Dog.

The last thing to solve was how to get a machine account in this state from a low privileged user, as until now I was manually modifying the machine account as an admin. Thankfully Kevin Robertson's amazing post on the Machine Account Quota helped tremendously. It explains that the creator of the machine account has control over various attributes including the samAccountName and ServicePrincipalName. Another problem I was running into was trying to change the samaccountname; when I was attempting to change it to match the DC minus the $, I was getting the following error:


As Kevin mentions in his post:

“If you modify the samAccountName, DnsHostname, or msDS-AdditionalDnsHostName attributes, the SPN list will automatically update with the new values.”

The SPN it was trying to set was already an SPN belonging to the target DC. Ceri suggested removing the SPNs before changing the samaccountname, which worked.

Lastly, until now I was removing the machine account after requesting the TGT (which requires admin privileges), I had to test whether disabling it or renaming it worked too. Disabling it resulted in a S_PRINCIPAL_UNKNOWN error being returned by the DC when requesting the S4U2self but renaming it worked. 

 

Checking If Exploitable

To exploit this requires 3 things, at least 1 DC not patched with either KB5008380 or KB5008602, any valid domain user account and a Machine Account Quota (MAQ) above 0.

Determining whether or not a DC is patched is very easy. Using my additional /nopac switch to Rubeus' asktgt, request a TGT without a PAC, if the DC is vulnerable, it will look like the following:


 Look at the size of the returned TGT. If the DC is not vulnerable the TGT will look as follows:


The size difference is immediately obvious. The next thing to check would be the MAQ: 


By default, it is 10 as above but can be changed, anything above 0 will do. Lastly, we need to check the SeMachineAccountPrivilege which is granted to Authenticated Users by default: 


If everything checks out, we can exploit this issue.

 

The Full Attack

The first step was to create a machine account we could use for the attack; the account I created was called TestSPN$). Kevin's Powermad worked nicely for this: 


After this, PowerView's Set-DomainObject can be used to clear the SPNs from the machine account: 


Changing the machine account's samaccountname can be done using Powermad's Set-MachineAccountAttribute. Here I changed it to IDC1 because the DC's samaccountname is IDC1$:


Rubeus' asktgt can be leveraged to request a TGT for that newly created machine account (This is just a normal TGT for the machine we had just created but using its new samaccontname): 


Set-MachineAccountAttribute can again be used to change the machine accounts samaccountname, either back to what it was or something else entirely, it does not matter:


 With the machine account renamed, it is now possible to request an S4U2self ticket using the retrieved TGT and get an ST encrypted with the DC's key, at the same time we can rewrite the sname within the ticket to be the LDAP service: 


Here, I impersonated the Administrator user for the LDAP service on the DC. It is worth noting that this could be any user on any service on any system on the domain.

The ticket was successfully injected into LSA as shown below:


Using that ticket, it was possible to DCSync:


The commands I ran to do this are shown below:


 
Mitigation / Detection

The best way to mitigate against this is to install the Microsoft patch (KB5008602), this patch fixes the issue with PAC confusion as well as fixing the issue with S4U2self created by the earlier KB5008380 patch.

Setting the Machine Account Quota to 0 is a quick and easy fix for stopping low privileged users from being able to create machine accounts, another related fix is to remove Authenticated Users from the SeMachineAccountPrivilege and adding Domain Admins or another group of allowed accounts.

There are several events caused by various steps which would be useful for determining attempts to perform this attack. The credit for determining these events should go entirely to Andrew Schwartz, I simply sent my logs to him after I performed the attack.

 
Machine Account Creation

Firstly, there is a 5156 event of an inbound connection to LDAP to create the machine account. For this event ID Andrew leveraged the research of “A Voyage to Uncovering Telemetry: Identifying RPC Telemetry for Detection Engineers” By: Jonathan Johnson:


Immediately followed by a 4673 event, which is the usage of the SeMachineAccountPrivilege:


In addition a 4741 event, describing the creation of the machine account:


And a 4724 event, regarding the password reset of the newly create machine account:


 
Clearing The SPNs

Next a 4742 event can be seen when the SPNs are removed from the machine account, this will show for the Service Principal Names field, as shown below:


 

Changing the sAMAccountName

A 4742 event will also show when the sAMAccountName is changed, and the new name will be shown in the SAM Account Name field:


More interestingly, a 4781 event will show both the old account name and the new account name:


 

Get TGT

When retrieving the TGT, a 4768 event will show, interestingly the Account Name field will show the new name of the account, while the User ID field will show the old name:


Then the account name change happens again with the 2 events mentioned above.

 

S4U2self

Lastly, as Elad mentions in his “Wagging the Dog” post, event 4769 fires, this time, however, some discrepancy is shown between Account Name and Service Name, while the Service Name field has the proper machine account name, the Account Name is missing the trailing dollar ($):

 

Conclusion

With the November 9th updates, many changes were made to AD, and I wouldn't be surprised if many other avenues existed using those issues but the one I use goes directly from a low privileged user to full domain takeover with a default configuration.

Ensuring all DCs are fully patched should be the main priority here as it will only take 1 unpatched DC for domain takeover to be possible.

In addition to patching, proper AD hardening with decent monitoring will always minimise the impact of any compromise significantly.

No comments:

Post a Comment